Added files details sidebar panel to assign/unassign/rename/delete system tags.remotes/origin/comments-markallread-dav
parent
8d41cbb97a
commit
ffba6d0a7e
@ -0,0 +1,40 @@ |
||||
<?php |
||||
/** |
||||
* @author Vincent Petry <pvince81@owncloud.com> |
||||
* |
||||
* @copyright Copyright (c) 2015, ownCloud, Inc. |
||||
* @license AGPL-3.0 |
||||
* |
||||
* This code is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License, version 3, |
||||
* as published by the Free Software Foundation. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License, version 3, |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
||||
* |
||||
*/ |
||||
|
||||
$eventDispatcher = \OC::$server->getEventDispatcher(); |
||||
$eventDispatcher->addListener( |
||||
'OCA\Files::loadAdditionalScripts', |
||||
function() { |
||||
// FIXME: no public API for these ? |
||||
\OC_Util::addVendorScript('select2/select2'); |
||||
\OC_Util::addVendorStyle('select2/select2'); |
||||
\OCP\Util::addScript('select2-toggleselect'); |
||||
\OCP\Util::addScript('oc-backbone-webdav'); |
||||
\OCP\Util::addScript('systemtags/systemtagmodel'); |
||||
\OCP\Util::addScript('systemtags/systemtagsmappingcollection'); |
||||
\OCP\Util::addScript('systemtags/systemtagscollection'); |
||||
\OCP\Util::addScript('systemtags/systemtagsinputfield'); |
||||
\OCP\Util::addScript('systemtags', 'app'); |
||||
\OCP\Util::addScript('systemtags', 'filesplugin'); |
||||
\OCP\Util::addScript('systemtags', 'systemtagsinfoview'); |
||||
\OCP\Util::addStyle('systemtags'); |
||||
} |
||||
); |
@ -0,0 +1,18 @@ |
||||
<?xml version="1.0"?> |
||||
<info> |
||||
<id>systemtags</id> |
||||
<name>System tags</name> |
||||
<description>System-wide tags user interface</description> |
||||
<licence>AGPL</licence> |
||||
<author>Vincent Petry</author> |
||||
<shipped>true</shipped> |
||||
<standalone/> |
||||
<default_enable/> |
||||
<version>0.1</version> |
||||
<dependencies> |
||||
<owncloud min-version="9.0" /> |
||||
</dependencies> |
||||
<documentation> |
||||
<user>user-systemtags</user> |
||||
</documentation> |
||||
</info> |
@ -0,0 +1,20 @@ |
||||
/* |
||||
* Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com> |
||||
* |
||||
* This file is licensed under the Affero General Public License version 3 |
||||
* or later. |
||||
* |
||||
* See the COPYING-README file. |
||||
* |
||||
*/ |
||||
|
||||
(function() { |
||||
if (!OCA.SystemTags) { |
||||
/** |
||||
* @namespace |
||||
*/ |
||||
OCA.SystemTags = {}; |
||||
} |
||||
|
||||
})(); |
||||
|
@ -0,0 +1,41 @@ |
||||
/* |
||||
* Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com> |
||||
* |
||||
* This file is licensed under the Affero General Public License version 3 |
||||
* or later. |
||||
* |
||||
* See the COPYING-README file. |
||||
* |
||||
*/ |
||||
|
||||
(function() { |
||||
OCA.SystemTags = _.extend({}, OCA.SystemTags); |
||||
if (!OCA.SystemTags) { |
||||
/** |
||||
* @namespace |
||||
*/ |
||||
OCA.SystemTags = {}; |
||||
} |
||||
|
||||
/** |
||||
* @namespace |
||||
*/ |
||||
OCA.SystemTags.FilesPlugin = { |
||||
allowedLists: [ |
||||
'files', |
||||
'favorites' |
||||
], |
||||
|
||||
attach: function(fileList) { |
||||
if (this.allowedLists.indexOf(fileList.id) < 0) { |
||||
return; |
||||
} |
||||
|
||||
fileList.registerDetailView(new OCA.SystemTags.SystemTagsInfoView()); |
||||
} |
||||
}; |
||||
|
||||
})(); |
||||
|
||||
OC.Plugins.register('OCA.Files.FileList', OCA.SystemTags.FilesPlugin); |
||||
|
@ -0,0 +1,135 @@ |
||||
/* |
||||
* Copyright (c) 2015 |
||||
* |
||||
* This file is licensed under the Affero General Public License version 3 |
||||
* or later. |
||||
* |
||||
* See the COPYING-README file. |
||||
* |
||||
*/ |
||||
|
||||
(function(OCA) { |
||||
/** |
||||
* @class OCA.SystemTags.SystemTagsInfoView |
||||
* @classdesc |
||||
* |
||||
* Displays a file's system tags |
||||
* |
||||
*/ |
||||
var SystemTagsInfoView = OCA.Files.DetailFileInfoView.extend( |
||||
/** @lends OCA.SystemTags.SystemTagsInfoView.prototype */ { |
||||
|
||||
_rendered: false, |
||||
|
||||
className: 'systemTagsInfoView hidden', |
||||
|
||||
/** |
||||
* @type OC.SystemTags.SystemTagsInputField |
||||
*/ |
||||
_inputView: null, |
||||
|
||||
initialize: function(options) { |
||||
var self = this; |
||||
options = options || {}; |
||||
|
||||
this._inputView = new OC.SystemTags.SystemTagsInputField({ |
||||
multiple: true, |
||||
allowActions: true, |
||||
allowCreate: true, |
||||
initSelection: function(element, callback) { |
||||
callback(self.selectedTagsCollection.toJSON()); |
||||
} |
||||
}); |
||||
|
||||
this.selectedTagsCollection = new OC.SystemTags.SystemTagsMappingCollection([], {objectType: 'files'}); |
||||
|
||||
this._inputView.collection.on('change:name', this._onTagRenamedGlobally, this); |
||||
this._inputView.collection.on('remove', this._onTagDeletedGlobally, this); |
||||
|
||||
this._inputView.on('select', this._onSelectTag, this); |
||||
this._inputView.on('deselect', this._onDeselectTag, this); |
||||
}, |
||||
|
||||
/** |
||||
* Event handler whenever a tag was selected |
||||
*/ |
||||
_onSelectTag: function(tag) { |
||||
// create a mapping entry for this tag
|
||||
this.selectedTagsCollection.create(tag.toJSON()); |
||||
}, |
||||
|
||||
/** |
||||
* Event handler whenever a tag gets deselected. |
||||
* Removes the selected tag from the mapping collection. |
||||
* |
||||
* @param {string} tagId tag id |
||||
*/ |
||||
_onDeselectTag: function(tagId) { |
||||
this.selectedTagsCollection.get(tagId).destroy(); |
||||
}, |
||||
|
||||
/** |
||||
* Event handler whenever a tag was renamed globally. |
||||
* |
||||
* This will automatically adjust the tag mapping collection to |
||||
* container the new name. |
||||
* |
||||
* @param {OC.Backbone.Model} changedTag tag model that has changed |
||||
*/ |
||||
_onTagRenamedGlobally: function(changedTag) { |
||||
// also rename it in the selection, if applicable
|
||||
var selectedTagMapping = this.selectedTagsCollection.get(changedTag.id); |
||||
if (selectedTagMapping) { |
||||
selectedTagMapping.set(changedTag.toJSON()); |
||||
} |
||||
}, |
||||
|
||||
/** |
||||
* Event handler whenever a tag was deleted globally. |
||||
* |
||||
* This will automatically adjust the tag mapping collection to |
||||
* container the new name. |
||||
* |
||||
* @param {OC.Backbone.Model} changedTag tag model that has changed |
||||
*/ |
||||
_onTagDeletedGlobally: function(tagId) { |
||||
// also rename it in the selection, if applicable
|
||||
this.selectedTagsCollection.remove(tagId); |
||||
}, |
||||
|
||||
setFileInfo: function(fileInfo) { |
||||
var self = this; |
||||
if (!this._rendered) { |
||||
this.render(); |
||||
} |
||||
|
||||
if (fileInfo) { |
||||
this.selectedTagsCollection.setObjectId(fileInfo.id); |
||||
this.selectedTagsCollection.fetch({ |
||||
success: function(collection) { |
||||
collection.fetched = true; |
||||
self._inputView.setData(collection.toJSON()); |
||||
self.$el.removeClass('hidden'); |
||||
} |
||||
}); |
||||
} |
||||
this.$el.addClass('hidden'); |
||||
}, |
||||
|
||||
/** |
||||
* Renders this details view |
||||
*/ |
||||
render: function() { |
||||
this.$el.append(this._inputView.$el); |
||||
this._inputView.render(); |
||||
}, |
||||
|
||||
remove: function() { |
||||
this._inputView.remove(); |
||||
} |
||||
}); |
||||
|
||||
OCA.SystemTags.SystemTagsInfoView = SystemTagsInfoView; |
||||
|
||||
})(OCA); |
||||
|
@ -0,0 +1,149 @@ |
||||
/** |
||||
* ownCloud |
||||
* |
||||
* @author Vincent Petry |
||||
* @copyright 2016 Vincent Petry <pvince81@owncloud.com> |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE |
||||
* License as published by the Free Software Foundation; either |
||||
* version 3 of the License, or any later version. |
||||
* |
||||
* This library is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU AFFERO GENERAL PUBLIC LICENSE for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public |
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
|
||||
describe('OCA.SystemTags.SystemTagsInfoView tests', function() { |
||||
var view; |
||||
|
||||
beforeEach(function() { |
||||
view = new OCA.SystemTags.SystemTagsInfoView(); |
||||
$('#testArea').append(view.$el); |
||||
}); |
||||
afterEach(function() { |
||||
view.remove(); |
||||
view = undefined; |
||||
}); |
||||
describe('rendering', function() { |
||||
it('renders input field view', function() { |
||||
view.render(); |
||||
expect(view.$el.find('input[name=tags]').length).toEqual(1); |
||||
}); |
||||
it('fetches selected tags then renders when setting file info', function() { |
||||
var fetchStub = sinon.stub(OC.SystemTags.SystemTagsMappingCollection.prototype, 'fetch'); |
||||
var setDataStub = sinon.stub(OC.SystemTags.SystemTagsInputField.prototype, 'setData'); |
||||
|
||||
expect(view.$el.hasClass('hidden')).toEqual(true); |
||||
|
||||
view.setFileInfo({id: '123'}); |
||||
expect(view.$el.find('input[name=tags]').length).toEqual(1); |
||||
|
||||
expect(fetchStub.calledOnce).toEqual(true); |
||||
expect(view.selectedTagsCollection.url()) |
||||
.toEqual(OC.linkToRemote('dav') + '/systemtags-relations/files/123'); |
||||
|
||||
view.selectedTagsCollection.add([ |
||||
{id: '1', name: 'test1'}, |
||||
{id: '3', name: 'test3'} |
||||
]); |
||||
|
||||
fetchStub.yieldTo('success', view.selectedTagsCollection); |
||||
expect(setDataStub.calledOnce).toEqual(true); |
||||
expect(setDataStub.getCall(0).args[0]).toEqual([{ |
||||
id: '1', name: 'test1', userVisible: true, userAssignable: true |
||||
}, { |
||||
id: '3', name: 'test3', userVisible: true, userAssignable: true |
||||
}]); |
||||
|
||||
expect(view.$el.hasClass('hidden')).toEqual(false); |
||||
|
||||
fetchStub.restore(); |
||||
setDataStub.restore(); |
||||
}); |
||||
it('overrides initSelection to use the local collection', function() { |
||||
var inputViewSpy = sinon.spy(OC.SystemTags, 'SystemTagsInputField'); |
||||
var element = $('<input type="hidden" val="1,3"/>'); |
||||
view.remove(); |
||||
view = new OCA.SystemTags.SystemTagsInfoView(); |
||||
view.selectedTagsCollection.add([ |
||||
{id: '1', name: 'test1'}, |
||||
{id: '3', name: 'test3'} |
||||
]); |
||||
|
||||
var callback = sinon.stub(); |
||||
inputViewSpy.getCall(0).args[0].initSelection(element, callback); |
||||
|
||||
expect(callback.calledOnce).toEqual(true); |
||||
expect(callback.getCall(0).args[0]).toEqual([{ |
||||
id: '1', name: 'test1', userVisible: true, userAssignable: true |
||||
}, { |
||||
id: '3', name: 'test3', userVisible: true, userAssignable: true |
||||
}]); |
||||
|
||||
inputViewSpy.restore(); |
||||
}); |
||||
}); |
||||
describe('events', function() { |
||||
var allTagsCollection; |
||||
beforeEach(function() { |
||||
allTagsCollection = view._inputView.collection; |
||||
|
||||
allTagsCollection.add([ |
||||
{id: '1', name: 'test1'}, |
||||
{id: '2', name: 'test2'}, |
||||
{id: '3', name: 'test3'} |
||||
]); |
||||
|
||||
view.selectedTagsCollection.add([ |
||||
{id: '1', name: 'test1'}, |
||||
{id: '3', name: 'test3'} |
||||
]); |
||||
view.render(); |
||||
}); |
||||
|
||||
it('renames model in selection collection on rename', function() { |
||||
allTagsCollection.get('3').set('name', 'test3_renamed'); |
||||
|
||||
expect(view.selectedTagsCollection.get('3').get('name')).toEqual('test3_renamed'); |
||||
}); |
||||
|
||||
it('adds tag to selection collection when selected by input', function() { |
||||
var createStub = sinon.stub(OC.SystemTags.SystemTagsMappingCollection.prototype, 'create'); |
||||
view._inputView.trigger('select', allTagsCollection.get('2')); |
||||
|
||||
expect(createStub.calledOnce).toEqual(true); |
||||
expect(createStub.getCall(0).args[0]).toEqual({ |
||||
id: '2', |
||||
name: 'test2', |
||||
userVisible: true, |
||||
userAssignable: true |
||||
}); |
||||
|
||||
createStub.restore(); |
||||
}); |
||||
it('removes tag from selection collection when deselected by input', function() { |
||||
var destroyStub = sinon.stub(OC.SystemTags.SystemTagModel.prototype, 'destroy'); |
||||
view._inputView.trigger('deselect', '3'); |
||||
|
||||
expect(destroyStub.calledOnce).toEqual(true); |
||||
expect(destroyStub.calledOn(view.selectedTagsCollection.get('3'))).toEqual(true); |
||||
|
||||
destroyStub.restore(); |
||||
}); |
||||
|
||||
it('removes tag from selection whenever the tag was deleted globally', function() { |
||||
expect(view.selectedTagsCollection.get('3')).not.toBeFalsy(); |
||||
|
||||
allTagsCollection.remove('3'); |
||||
|
||||
expect(view.selectedTagsCollection.get('3')).toBeFalsy(); |
||||
|
||||
}); |
||||
}); |
||||
}); |
@ -0,0 +1,80 @@ |
||||
/* |
||||
* Copyright (c) 2016 |
||||
* |
||||
* This file is licensed under the Affero General Public License version 3 |
||||
* or later. |
||||
* |
||||
* See the COPYING-README file. |
||||
* |
||||
*/ |
||||
.systemtags-select2-dropdown .select2-selected { |
||||
display: list-item; |
||||
background-color: #f8f8f8; |
||||
} |
||||
.systemtags-select2-dropdown .select2-highlighted, |
||||
.systemtags-select2-dropdown .select2-selected.select2-highlighted { |
||||
background: #3875d7; |
||||
} |
||||
|
||||
.systemtags-select2-dropdown .select2-highlighted { |
||||
color: #000000; |
||||
} |
||||
.systemtags-select2-dropdown .select2-result-label .checkmark { |
||||
visibility: hidden; |
||||
} |
||||
|
||||
.systemtags-select2-dropdown .select2-result-label .new-item .systemtags-actions { |
||||
display: none; |
||||
} |
||||
|
||||
.systemtags-select2-dropdown .select2-selected .select2-result-label .checkmark { |
||||
visibility: visible; |
||||
} |
||||
|
||||
.systemtags-select2-dropdown .select2-result-label .icon { |
||||
display: inline-block; |
||||
} |
||||
|
||||
.systemtags-select2-dropdown .systemtags-actions { |
||||
float: right; |
||||
} |
||||
|
||||
.systemtags-select2-dropdown .systemtags-rename-form { |
||||
display: inline; |
||||
margin-left: 10px; |
||||
} |
||||
|
||||
.systemtags-select2-container { |
||||
width: 80%; |
||||
} |
||||
|
||||
.systemtags-select2-container .select2-choices { |
||||
white-space: nowrap; |
||||
text-overflow: ellipsis; |
||||
background: #fff; |
||||
color: #555; |
||||
box-sizing: content-box; |
||||
border-radius: 3px; |
||||
border: 1px solid #ddd; |
||||
margin: 3px 3px 3px 0; |
||||
padding: 7px 6px 5px; |
||||
min-height: auto; |
||||
} |
||||
|
||||
.systemtags-select2-container .select2-choices .select2-search-choice { |
||||
border: 0; |
||||
box-shadow: none; |
||||
background: none; |
||||
padding: 0; |
||||
margin: 0; |
||||
line-height: 20px; |
||||
} |
||||
.systemtags-select2-container .select2-choices .select2-search-choice-close { |
||||
display: none; |
||||
} |
||||
.systemtags-select2-container .select2-choices .select2-search-field input { |
||||
margin: 0; |
||||
padding: 0; |
||||
line-height: 20px; |
||||
} |
||||
|
@ -0,0 +1,52 @@ |
||||
/* |
||||
* Copyright (c) 2015 |
||||
* |
||||
* This file is licensed under the Affero General Public License version 3 |
||||
* or later. |
||||
* |
||||
* See the COPYING-README file. |
||||
* |
||||
*/ |
||||
|
||||
(function(OC) { |
||||
var NS_OWNCLOUD = 'http://owncloud.org/ns'; |
||||
/** |
||||
* @class OCA.SystemTags.SystemTagsCollection |
||||
* @classdesc |
||||
* |
||||
* System tag |
||||
* |
||||
*/ |
||||
var SystemTagModel = OC.Backbone.Model.extend( |
||||
/** @lends OCA.SystemTags.SystemTagModel.prototype */ { |
||||
sync: OC.Backbone.davSync, |
||||
|
||||
defaults: { |
||||
userVisible: true, |
||||
userAssignable: true |
||||
}, |
||||
|
||||
davProperties: { |
||||
'id': '{' + NS_OWNCLOUD + '}id', |
||||
'name': '{' + NS_OWNCLOUD + '}display-name', |
||||
'userVisible': '{' + NS_OWNCLOUD + '}user-visible', |
||||
'userAssignable': '{' + NS_OWNCLOUD + '}user-assignable' |
||||
}, |
||||
|
||||
parse: function(data) { |
||||
return { |
||||
id: data.id, |
||||
name: data.name, |
||||
userVisible: data.userVisible === '1', |
||||
userAssignable: data.userAssignable === '1' |
||||
}; |
||||
} |
||||
}); |
||||
|
||||
/** |
||||
* @namespace |
||||
*/ |
||||
OC.SystemTags = OC.SystemTags || {}; |
||||
OC.SystemTags.SystemTagModel = SystemTagModel; |
||||
})(OC); |
||||
|
@ -0,0 +1,89 @@ |
||||
/* |
||||
* Copyright (c) 2015 |
||||
* |
||||
* This file is licensed under the Affero General Public License version 3 |
||||
* or later. |
||||
* |
||||
* See the COPYING-README file. |
||||
* |
||||
*/ |
||||
|
||||
(function(OC) { |
||||
|
||||
function filterFunction(model, term) { |
||||
return model.get('name').substr(0, term.length) === term; |
||||
} |
||||
|
||||
/** |
||||
* @class OCA.SystemTags.SystemTagsCollection |
||||
* @classdesc |
||||
* |
||||
* Collection of tags assigned to a file |
||||
* |
||||
*/ |
||||
var SystemTagsCollection = OC.Backbone.Collection.extend( |
||||
/** @lends OC.SystemTags.SystemTagsCollection.prototype */ { |
||||
|
||||
sync: OC.Backbone.davSync, |
||||
|
||||
model: OC.SystemTags.SystemTagModel, |
||||
|
||||
url: function() { |
||||
return OC.linkToRemote('dav') + '/systemtags/'; |
||||
}, |
||||
|
||||
filterByName: function(name) { |
||||
return this.filter(function(model) { |
||||
return filterFunction(model, name); |
||||
}); |
||||
}, |
||||
|
||||
reset: function() { |
||||
this.fetched = false; |
||||
return OC.Backbone.Collection.prototype.reset.apply(this, arguments); |
||||
}, |
||||
|
||||
/** |
||||
* Lazy fetch. |
||||
* Only fetches once, subsequent calls will directly call the success handler. |
||||
* |
||||
* @param options |
||||
* @param [options.force] true to force fetch even if cached entries exist |
||||
* |
||||
* @see Backbone.Collection#fetch |
||||
*/ |
||||
fetch: function(options) { |
||||
var self = this; |
||||
options = options || {}; |
||||
if (this.fetched || options.force) { |
||||
// directly call handler
|
||||
if (options.success) { |
||||
options.success(this, null, options); |
||||
} |
||||
// trigger sync event
|
||||
this.trigger('sync', this, null, options); |
||||
return Promise.resolve(); |
||||
} |
||||
|
||||
var success = options.success; |
||||
options = _.extend({}, options); |
||||
options.success = function() { |
||||
self.fetched = true; |
||||
if (success) { |
||||
return success.apply(this, arguments); |
||||
} |
||||
}; |
||||
|
||||
return OC.Backbone.Collection.prototype.fetch.call(this, options); |
||||
} |
||||
}); |
||||
|
||||
OC.SystemTags = OC.SystemTags || {}; |
||||
OC.SystemTags.SystemTagsCollection = SystemTagsCollection; |
||||
|
||||
/** |
||||
* @type OC.SystemTags.SystemTagsCollection |
||||
*/ |
||||
OC.SystemTags.collection = new OC.SystemTags.SystemTagsCollection(); |
||||
})(OC); |
||||
|
@ -0,0 +1,372 @@ |
||||
/* |
||||
* 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) { |
||||
var TEMPLATE = |
||||
'<input class="systemTagsInputField" type="hidden" name="tags" value=""/>'; |
||||
|
||||
var RESULT_TEMPLATE = |
||||
'<span class="systemtags-item{{#if isNew}} new-item{{/if}}" data-id="{{id}}">' + |
||||
' <span class="checkmark icon icon-checkmark"></span>' + |
||||
' <span class="label">{{name}}</span>' + |
||||
'{{#allowActions}}' + |
||||
' <span class="systemtags-actions">' + |
||||
' <a href="#" class="rename icon icon-rename" title="{{renameTooltip}}"></a>' + |
||||
' </span>' + |
||||
'{{/allowActions}}' + |
||||
'</span>'; |
||||
|
||||
var RENAME_FORM_TEMPLATE = |
||||
'<form class="systemtags-rename-form">' + |
||||
' <label class="hidden-visually" for="{{cid}}-rename-input">{{renameLabel}}</label>' + |
||||
' <input id="{{cid}}-rename-input" type="text" value="{{name}}">' + |
||||
' <a href="#" class="delete icon icon-delete" title="{{deleteTooltip}}"></a>' + |
||||
'</form>'; |
||||
|
||||
/** |
||||
* @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, |
||||
|
||||
className: 'systemTagsInputFieldContainer', |
||||
|
||||
template: function(data) { |
||||
if (!this._template) { |
||||
this._template = Handlebars.compile(TEMPLATE); |
||||
} |
||||
return this._template(data); |
||||
}, |
||||
|
||||
/** |
||||
* 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 {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; |
||||
|
||||
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); |
||||
}); |
||||
|
||||
_.bindAll( |
||||
this, |
||||
'_refreshSelection', |
||||
'_onClickRenameTag', |
||||
'_onClickDeleteTag', |
||||
'_onSelectTag', |
||||
'_onDeselectTag', |
||||
'_onSubmitRenameTag' |
||||
); |
||||
}, |
||||
|
||||
/** |
||||
* 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); |
||||
if (!this._renameFormTemplate) { |
||||
this._renameFormTemplate = Handlebars.compile(RENAME_FORM_TEMPLATE); |
||||
} |
||||
|
||||
var oldName = tagModel.get('name'); |
||||
var $renameForm = $(this._renameFormTemplate({ |
||||
cid: this.cid, |
||||
name: oldName, |
||||
deleteTooltip: t('core', 'Delete'), |
||||
renameLabel: t('core', 'Rename'), |
||||
})); |
||||
$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(); |
||||
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(); |
||||
$item.closest('.select2-result').remove(); |
||||
// TODO: spinner
|
||||
return false; |
||||
}, |
||||
|
||||
/** |
||||
* 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, |
||||
userVisible: true, |
||||
userAssignable: true |
||||
}, { |
||||
success: function(model) { |
||||
var data = self.$tagsField.select2('data'); |
||||
data.push(model.toJSON()); |
||||
self.$tagsField.select2('data', data); |
||||
self.trigger('select', model); |
||||
} |
||||
}); |
||||
this.$tagsField.select2('close'); |
||||
e.preventDefault(); |
||||
return false; |
||||
} else { |
||||
tag = this.collection.get(e.object.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() { |
||||
query.callback({ |
||||
results: _.invoke(self.collection.filterByName(query.term), 'toJSON') |
||||
}); |
||||
} |
||||
}); |
||||
}, |
||||
|
||||
_preventDefault: function(e) { |
||||
e.stopPropagation(); |
||||
}, |
||||
|
||||
/** |
||||
* Formats a single dropdown result |
||||
* |
||||
* @param {Object} data data to format |
||||
* @return {string} HTML markup |
||||
*/ |
||||
_formatDropDownResult: function(data) { |
||||
if (!this._resultTemplate) { |
||||
this._resultTemplate = Handlebars.compile(RESULT_TEMPLATE); |
||||
} |
||||
return this._resultTemplate(_.extend({ |
||||
renameTooltip: t('core', 'Rename'), |
||||
allowActions: this._allowActions |
||||
}, 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) { |
||||
if (this.collection.filterByName(term).length) { |
||||
return; |
||||
} |
||||
if (!this._newTag) { |
||||
this._newTag = { |
||||
id: -1, |
||||
name: term, |
||||
isNew: true |
||||
}; |
||||
} else { |
||||
this._newTag.name = term; |
||||
} |
||||
|
||||
return this._newTag; |
||||
}, |
||||
|
||||
_initSelection: function(element, callback) { |
||||
var self = this; |
||||
var ids = $(element).val().split(','); |
||||
|
||||
function findSelectedObjects(ids) { |
||||
var selectedModels = self.collection.filter(function(model) { |
||||
return ids.indexOf(model.id) >= 0; |
||||
}); |
||||
return _.invoke(selectedModels, 'toJSON'); |
||||
} |
||||
|
||||
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', 'Global 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: function(tag) { |
||||
return '<span class="label">' + escapeHTML(tag.name) + '</span>' + |
||||
'<span class="comma">, </span>'; |
||||
}, |
||||
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) { |
||||
return OC.Util.naturalSortCompare(a.name, b.name); |
||||
} |
||||
if (aSelected && !bSelected) { |
||||
return -1; |
||||
} |
||||
return 1; |
||||
}); |
||||
return results; |
||||
} |
||||
}) |
||||
.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'); |
||||
} |
||||
}, |
||||
|
||||
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); |
||||
|
@ -0,0 +1,87 @@ |
||||
/* |
||||
* Copyright (c) 2015 |
||||
* |
||||
* This file is licensed under the Affero General Public License version 3 |
||||
* or later. |
||||
* |
||||
* See the COPYING-README file. |
||||
* |
||||
*/ |
||||
|
||||
(function(OC) { |
||||
/** |
||||
* @class OC.SystemTags.SystemTagsMappingCollection |
||||
* @classdesc |
||||
* |
||||
* Collection of tags assigned to a an object |
||||
* |
||||
*/ |
||||
var SystemTagsMappingCollection = OC.Backbone.Collection.extend( |
||||
/** @lends OC.SystemTags.SystemTagsMappingCollection.prototype */ { |
||||
|
||||
sync: OC.Backbone.davSync, |
||||
|
||||
/** |
||||
* Use PUT instead of PROPPATCH |
||||
*/ |
||||
usePUT: true, |
||||
|
||||
/** |
||||
* Id of the file for which to filter activities by |
||||
* |
||||
* @var int |
||||
*/ |
||||
_objectId: null, |
||||
|
||||
/** |
||||
* Type of the object to filter by |
||||
* |
||||
* @var string |
||||
*/ |
||||
_objectType: 'files', |
||||
|
||||
model: OC.SystemTags.SystemTagModel, |
||||
|
||||
url: function() { |
||||
return OC.linkToRemote('dav') + '/systemtags-relations/' + this._objectType + '/' + this._objectId; |
||||
}, |
||||
|
||||
/** |
||||
* Sets the object id to filter by or null for all. |
||||
* |
||||
* @param {int} objectId file id or null |
||||
*/ |
||||
setObjectId: function(objectId) { |
||||
this._objectId = objectId; |
||||
}, |
||||
|
||||
/** |
||||
* Sets the object type to filter by or null for all. |
||||
* |
||||
* @param {int} objectType file id or null |
||||
*/ |
||||
setObjectType: function(objectType) { |
||||
this._objectType = objectType; |
||||
}, |
||||
|
||||
initialize: function(models, options) { |
||||
options = options || {}; |
||||
if (!_.isUndefined(options.objectId)) { |
||||
this._objectId = options.objectId; |
||||
} |
||||
if (!_.isUndefined(options.objectType)) { |
||||
this._objectType = options.objectType; |
||||
} |
||||
}, |
||||
|
||||
getTagIds: function() { |
||||
return this.map(function(model) { |
||||
return model.id; |
||||
}); |
||||
} |
||||
}); |
||||
|
||||
OC.SystemTags = OC.SystemTags || {}; |
||||
OC.SystemTags.SystemTagsMappingCollection = SystemTagsMappingCollection; |
||||
})(OC); |
||||
|
@ -0,0 +1,84 @@ |
||||
/** |
||||
* ownCloud |
||||
* |
||||
* @author Vincent Petry |
||||
* @copyright 2016 Vincent Petry <pvince81@owncloud.com> |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE |
||||
* License as published by the Free Software Foundation; either |
||||
* version 3 of the License, or any later version. |
||||
* |
||||
* This library is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU AFFERO GENERAL PUBLIC LICENSE for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public |
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
|
||||
describe('OC.SystemTags.SystemTagsCollection tests', function() { |
||||
var collection; |
||||
|
||||
beforeEach(function() { |
||||
collection = new OC.SystemTags.SystemTagsCollection(); |
||||
}); |
||||
it('fetches only once, until reset', function() { |
||||
var syncStub = sinon.stub(collection, 'sync'); |
||||
var callback = sinon.stub(); |
||||
var callback2 = sinon.stub(); |
||||
var callback3 = sinon.stub(); |
||||
var eventHandler = sinon.stub(); |
||||
|
||||
collection.on('sync', eventHandler); |
||||
|
||||
collection.fetch({ |
||||
success: callback |
||||
}); |
||||
|
||||
expect(callback.notCalled).toEqual(true); |
||||
expect(syncStub.calledOnce).toEqual(true); |
||||
expect(eventHandler.notCalled).toEqual(true); |
||||
|
||||
syncStub.yieldTo('success', collection); |
||||
|
||||
expect(callback.calledOnce).toEqual(true); |
||||
expect(callback.firstCall.args[0]).toEqual(collection); |
||||
expect(eventHandler.calledOnce).toEqual(true); |
||||
expect(eventHandler.firstCall.args[0]).toEqual(collection); |
||||
|
||||
collection.fetch({ |
||||
success: callback2 |
||||
}); |
||||
|
||||
expect(eventHandler.calledTwice).toEqual(true); |
||||
expect(eventHandler.secondCall.args[0]).toEqual(collection); |
||||
|
||||
// not re-called
|
||||
expect(syncStub.calledOnce).toEqual(true); |
||||
|
||||
expect(callback.calledOnce).toEqual(true); |
||||
expect(callback2.calledOnce).toEqual(true); |
||||
expect(callback2.firstCall.args[0]).toEqual(collection); |
||||
|
||||
expect(collection.fetched).toEqual(true); |
||||
|
||||
collection.reset(); |
||||
|
||||
expect(collection.fetched).toEqual(false); |
||||
|
||||
collection.fetch({ |
||||
success: callback3 |
||||
}); |
||||
|
||||
expect(syncStub.calledTwice).toEqual(true); |
||||
|
||||
syncStub.yieldTo('success', collection); |
||||
expect(callback3.calledOnce).toEqual(true); |
||||
expect(callback3.firstCall.args[0]).toEqual(collection); |
||||
|
||||
syncStub.restore(); |
||||
}); |
||||
}); |
@ -0,0 +1,308 @@ |
||||
/** |
||||
* ownCloud |
||||
* |
||||
* @author Vincent Petry |
||||
* @copyright 2016 Vincent Petry <pvince81@owncloud.com> |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE |
||||
* License as published by the Free Software Foundation; either |
||||
* version 3 of the License, or any later version. |
||||
* |
||||
* This library is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU AFFERO GENERAL PUBLIC LICENSE for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public |
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
* |
||||
*/ |
||||
|
||||
describe('OC.SystemTags.SystemTagsInputField tests', function() { |
||||
var view, select2Stub; |
||||
|
||||
beforeEach(function() { |
||||
var $container = $('<div class="testInputContainer"></div>'); |
||||
select2Stub = sinon.stub($.fn, 'select2'); |
||||
select2Stub.returnsThis(); |
||||
$('#testArea').append($container); |
||||
view = new OC.SystemTags.SystemTagsInputField(); |
||||
$container.append(view.$el); |
||||
}); |
||||
afterEach(function() { |
||||
select2Stub.restore(); |
||||
OC.SystemTags.collection.reset(); |
||||
view.remove(); |
||||
view = undefined; |
||||
}); |
||||
describe('rendering', function() { |
||||
beforeEach(function() { |
||||
view.render(); |
||||
}); |
||||
it('calls select2 on rendering', function() { |
||||
expect(view.$el.find('input[name=tags]').length).toEqual(1); |
||||
expect(select2Stub.called).toEqual(true); |
||||
}); |
||||
it('formatResult renders rename button', function() { |
||||
var opts = select2Stub.getCall(0).args[0]; |
||||
var $el = $(opts.formatResult({id: '1', name: 'test'})); |
||||
expect($el.find('.label').text()).toEqual('test'); |
||||
expect($el.find('.rename').length).toEqual(1); |
||||
}); |
||||
}); |
||||
describe('initSelection', function() { |
||||
var fetchStub; |
||||
var testTags; |
||||
|
||||
beforeEach(function() { |
||||
fetchStub = sinon.stub(OC.SystemTags.SystemTagsCollection.prototype, 'fetch'); |
||||
testTags = [ |
||||
new OC.SystemTags.SystemTagModel({id: '1', name: 'test1'}), |
||||
new OC.SystemTags.SystemTagModel({id: '2', name: 'test2'}), |
||||
new OC.SystemTags.SystemTagModel({id: '3', name: 'test3'}), |
||||
]; |
||||
view.render(); |
||||
}); |
||||
afterEach(function() { |
||||
fetchStub.restore(); |
||||
}); |
||||
it('grabs values from the full collection', function() { |
||||
var $el = view.$el.find('input'); |
||||
$el.val('1,3'); |
||||
var opts = select2Stub.getCall(0).args[0]; |
||||
var callback = sinon.stub(); |
||||
opts.initSelection($el, callback); |
||||
|
||||
expect(fetchStub.calledOnce).toEqual(true); |
||||
view.collection.add(testTags); |
||||
fetchStub.yieldTo('success', view.collection); |
||||
|
||||
expect(callback.calledOnce).toEqual(true); |
||||
var models = callback.getCall(0).args[0]; |
||||
expect(models.length).toEqual(2); |
||||
expect(models[0].id).toEqual('1'); |
||||
expect(models[0].name).toEqual('test1'); |
||||
expect(models[1].id).toEqual('3'); |
||||
expect(models[1].name).toEqual('test3'); |
||||
}); |
||||
}); |
||||
describe('tag selection', function() { |
||||
beforeEach(function() { |
||||
view.render(); |
||||
var $el = view.$el.find('input'); |
||||
$el.val('1'); |
||||
|
||||
view.collection.add([ |
||||
new OC.SystemTags.SystemTagModel({id: '1', name: 'abc'}), |
||||
new OC.SystemTags.SystemTagModel({id: '2', name: 'def'}), |
||||
new OC.SystemTags.SystemTagModel({id: '3', name: 'abd'}), |
||||
]); |
||||
}); |
||||
afterEach(function() { |
||||
}); |
||||
it('does not create dummy tag when user types non-matching name', function() { |
||||
var opts = select2Stub.getCall(0).args[0]; |
||||
var result = opts.createSearchChoice('abc'); |
||||
expect(result).not.toBeDefined(); |
||||
}); |
||||
it('creates dummy tag when user types non-matching name', function() { |
||||
var opts = select2Stub.getCall(0).args[0]; |
||||
var result = opts.createSearchChoice('abnew'); |
||||
expect(result.id).toEqual(-1); |
||||
expect(result.name).toEqual('abnew'); |
||||
expect(result.isNew).toEqual(true); |
||||
}); |
||||
it('creates the real tag and fires select event after user selects the dummy tag', function() { |
||||
var selectHandler = sinon.stub(); |
||||
view.on('select', selectHandler); |
||||
var createStub = sinon.stub(OC.SystemTags.SystemTagsCollection.prototype, 'create'); |
||||
view.$el.find('input').trigger(new $.Event('select2-selecting', { |
||||
object: { |
||||
id: -1, |
||||
name: 'newname', |
||||
isNew: true |
||||
} |
||||
})); |
||||
|
||||
expect(createStub.calledOnce).toEqual(true); |
||||
expect(createStub.getCall(0).args[0]).toEqual({ |
||||
name: 'newname', |
||||
userVisible: true, |
||||
userAssignable: true |
||||
}); |
||||
|
||||
var newModel = new OC.SystemTags.SystemTagModel({ |
||||
id: '123', |
||||
name: 'newname', |
||||
userVisible: true, |
||||
userAssignable: true |
||||
}); |
||||
|
||||
// not called yet
|
||||
expect(selectHandler.notCalled).toEqual(true); |
||||
|
||||
select2Stub.withArgs('data').returns([{ |
||||
id: '1', |
||||
name: 'abc' |
||||
}]); |
||||
|
||||
createStub.yieldTo('success', newModel); |
||||
|
||||
expect(select2Stub.lastCall.args[0]).toEqual('data'); |
||||
expect(select2Stub.lastCall.args[1]).toEqual([{ |
||||
id: '1', |
||||
name: 'abc' |
||||
}, |
||||
newModel.toJSON() |
||||
]); |
||||
|
||||
expect(selectHandler.calledOnce).toEqual(true); |
||||
expect(selectHandler.getCall(0).args[0]).toEqual(newModel); |
||||
|
||||
createStub.restore(); |
||||
}); |
||||
it('triggers select event after selecting an existing tag', function() { |
||||
var selectHandler = sinon.stub(); |
||||
view.on('select', selectHandler); |
||||
view.$el.find('input').trigger(new $.Event('select2-selecting', { |
||||
object: { |
||||
id: '2', |
||||
name: 'def' |
||||
} |
||||
})); |
||||
|
||||
expect(selectHandler.calledOnce).toEqual(true); |
||||
expect(selectHandler.getCall(0).args[0]).toEqual(view.collection.get('2')); |
||||
}); |
||||
it('triggers deselect event after deselecting an existing tag', function() { |
||||
var selectHandler = sinon.stub(); |
||||
view.on('deselect', selectHandler); |
||||
view.$el.find('input').trigger(new $.Event('select2-removing', { |
||||
choice: { |
||||
id: '2', |
||||
name: 'def' |
||||
} |
||||
})); |
||||
|
||||
expect(selectHandler.calledOnce).toEqual(true); |
||||
expect(selectHandler.getCall(0).args[0]).toEqual('2'); |
||||
}); |
||||
}); |
||||
describe('autocomplete', function() { |
||||
var fetchStub, opts; |
||||
|
||||
beforeEach(function() { |
||||
fetchStub = sinon.stub(OC.SystemTags.SystemTagsCollection.prototype, 'fetch'); |
||||
view.render(); |
||||
opts = select2Stub.getCall(0).args[0]; |
||||
|
||||
view.collection.add([ |
||||
new OC.SystemTags.SystemTagModel({id: '1', name: 'abc'}), |
||||
new OC.SystemTags.SystemTagModel({id: '2', name: 'def'}), |
||||
new OC.SystemTags.SystemTagModel({id: '3', name: 'abd'}), |
||||
]); |
||||
}); |
||||
afterEach(function() { |
||||
fetchStub.restore(); |
||||
}); |
||||
it('completes results', function() { |
||||
var callback = sinon.stub(); |
||||
opts.query({ |
||||
term: 'ab', |
||||
callback: callback |
||||
}); |
||||
expect(fetchStub.calledOnce).toEqual(true); |
||||
|
||||
fetchStub.yieldTo('success', view.collection); |
||||
|
||||
expect(callback.calledOnce).toEqual(true); |
||||
expect(callback.getCall(0).args[0].results).toEqual([ |
||||
{ |
||||
id: '1', |
||||
name: 'abc', |
||||
userVisible: true, |
||||
userAssignable: true |
||||
}, |
||||
{ |
||||
id: '3', |
||||
name: 'abd', |
||||
userVisible: true, |
||||
userAssignable: true |
||||
} |
||||
]); |
||||
}); |
||||
}); |
||||
describe('tag actions', function() { |
||||
var $dropdown, opts; |
||||
|
||||
beforeEach(function() { |
||||
$dropdown = $('<div class="select2-dropdown"></div>'); |
||||
select2Stub.withArgs('dropdown').returns($dropdown); |
||||
$('#testArea').append($dropdown); |
||||
|
||||
view.render(); |
||||
|
||||
opts = select2Stub.getCall(0).args[0]; |
||||
|
||||
view.collection.add([ |
||||
new OC.SystemTags.SystemTagModel({id: '1', name: 'abc'}), |
||||
]); |
||||
|
||||
$dropdown.append(opts.formatResult(view.collection.get('1').toJSON())); |
||||
|
||||
}); |
||||
afterEach(function() { |
||||
}); |
||||
it('displays rename form when clicking rename', function() { |
||||
$dropdown.find('.rename').mouseup(); |
||||
expect($dropdown.find('form.systemtags-rename-form').length).toEqual(1); |
||||
expect($dropdown.find('form.systemtags-rename-form input').val()).toEqual('abc'); |
||||
}); |
||||
it('renames model and submits change when submitting form', function() { |
||||
var saveStub = sinon.stub(OC.SystemTags.SystemTagModel.prototype, 'save'); |
||||
$dropdown.find('.rename').mouseup(); |
||||
$dropdown.find('form input').val('abc_renamed'); |
||||
$dropdown.find('form').trigger(new $.Event('submit')); |
||||
|
||||
expect(saveStub.calledOnce).toEqual(true); |
||||
expect(saveStub.getCall(0).args[0]).toEqual({'name': 'abc_renamed'}); |
||||
|
||||
expect($dropdown.find('.label').text()).toEqual('abc_renamed'); |
||||
expect($dropdown.find('form').length).toEqual(0); |
||||
|
||||
saveStub.restore(); |
||||
}); |
||||
it('deletes model and submits change when clicking delete', function() { |
||||
var destroyStub = sinon.stub(OC.SystemTags.SystemTagModel.prototype, 'destroy'); |
||||
|
||||
expect($dropdown.find('.delete').length).toEqual(0); |
||||
$dropdown.find('.rename').mouseup(); |
||||
// delete button appears
|
||||
expect($dropdown.find('.delete').length).toEqual(1); |
||||
$dropdown.find('.delete').mouseup(); |
||||
|
||||
expect(destroyStub.calledOnce).toEqual(true); |
||||
expect(destroyStub.calledOn(view.collection.get('1'))); |
||||
|
||||
destroyStub.restore(); |
||||
}); |
||||
}); |
||||
describe('setting data', function() { |
||||
beforeEach(function() { |
||||
view.render(); |
||||
}); |
||||
it('sets value when calling setValues', function() { |
||||
var vals = ['1', '2']; |
||||
view.setValues(vals); |
||||
expect(select2Stub.lastCall.args[0]).toEqual('val'); |
||||
expect(select2Stub.lastCall.args[1]).toEqual(vals); |
||||
}); |
||||
it('sets data when calling setData', function() { |
||||
var vals = [{id: '1', name: 'test1'}, {id: '2', name: 'test2'}]; |
||||
view.setData(vals); |
||||
expect(select2Stub.lastCall.args[0]).toEqual('data'); |
||||
expect(select2Stub.lastCall.args[1]).toEqual(vals); |
||||
}); |
||||
}); |
||||
}); |
Loading…
Reference in new issue