diff --git a/CHANGES.rst b/CHANGES.rst
index 3745d7150..4e83dbeda 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,7 +1,7 @@
Changelog
=========
-2.1.4 (unreleased)
+2.2.0 (unreleased)
------------------
Incompatibilities:
@@ -13,11 +13,45 @@ New:
- set XML syntax coloring for .pt files in text editor
[ebrehault]
+- Structure now accept customization options for a number of things in
+ the form of requirejs modules. This currently includes the extended
+ menuOptions definition, the menuGenerator per result item, the click
+ handler the link for each individual item, and the collection module
+ for interaction with the server side API for item generation.
+
+ Where applicable, the default implementation are now named requirejs
+ includes with those as the defaults to the relevant parameters.
+
+ Incidentally, this also required a major cleanup/refactoring of how
+ the ResultCollection class interacts with the pattern and its support
+ classes.
+ [metatoaster]
+
+- Structure now supports IPublishTraverse style subpaths for push state.
+ [metatoaster]
+
+- Alternative parameter/syntax for specification of the pushState url to
+ be inline with the usage of ``{path}`` token in URL templates.
+ [metatoaster]
+
+- Structure can use the ``viewURL`` from a returned data item, alongside
+ with the previous default of simply appending ``/view`` to the
+ ``getURL`` attribute if this was not provided, for its view URL,
+ [metatoaster]
+
Fixes:
- Fix ``Makefile`` to use ``mockup/build`` instead of ``build``.
[thet]
+- Fix structure so rendering does not fail when paste button is missing.
+ [metatoaster]
+
+- Fix structure so that different views can have its own saved visible
+ column ordering settings. Also loosen the coupling of the columns to
+ the data to aid in view rendering.
+ [metatoaster]
+
2.1.3 (2016-02-27)
New:
diff --git a/mockup/js/utils.js b/mockup/js/utils.js
index 90f83d83f..f4ada0d92 100644
--- a/mockup/js/utils.js
+++ b/mockup/js/utils.js
@@ -266,6 +266,14 @@ define([
.toString(16).substring(1));
};
+ var getWindow = function() {
+ var win = window;
+ if (win.parent !== window) {
+ win = win.parent;
+ }
+ return win;
+ };
+
return {
generateId: generateId,
parseBodyTag: function(txt) {
@@ -309,6 +317,7 @@ define([
return $el.val();
}
},
+ getWindow: getWindow,
featureSupport: {
/*
well tested feature support for things we use in mockup.
diff --git a/mockup/patterns/structure/js/actionmenu.js b/mockup/patterns/structure/js/actionmenu.js
new file mode 100644
index 000000000..8d3b86aa7
--- /dev/null
+++ b/mockup/patterns/structure/js/actionmenu.js
@@ -0,0 +1,93 @@
+define([
+], function() {
+ 'use strict';
+
+ var menuOptions = {
+ 'cutItem': [
+ 'mockup-patterns-structure-url/js/actions',
+ 'cutClicked',
+ '#',
+ 'Cut',
+ ],
+ 'copyItem': [
+ 'mockup-patterns-structure-url/js/actions',
+ 'copyClicked',
+ '#',
+ 'Copy'
+ ],
+ 'pasteItem': [
+ 'mockup-patterns-structure-url/js/actions',
+ 'pasteClicked',
+ '#',
+ 'Paste'
+ ],
+ 'move-top': [
+ 'mockup-patterns-structure-url/js/actions',
+ 'moveTopClicked',
+ '#',
+ 'Move to top of folder'
+ ],
+ 'move-bottom': [
+ 'mockup-patterns-structure-url/js/actions',
+ 'moveBottomClicked',
+ '#',
+ 'Move to bottom of folder'
+ ],
+ 'set-default-page': [
+ 'mockup-patterns-structure-url/js/actions',
+ 'setDefaultPageClicked',
+ '#',
+ 'Set as default page'
+ ],
+ 'selectAll': [
+ 'mockup-patterns-structure-url/js/actions',
+ 'selectAll',
+ '#',
+ 'Select all contained items'
+ ],
+ 'openItem': [
+ 'mockup-patterns-structure-url/js/navigation',
+ 'openClicked',
+ '#',
+ 'Open'
+ ],
+ 'editItem': [
+ 'mockup-patterns-structure-url/js/navigation',
+ 'editClicked',
+ '#',
+ 'Edit'
+ ],
+ };
+
+ var ActionMenu = function(menu) {
+ // If an explicit menu was specified as an option to AppView, this
+ // constructor will not override that.
+ if (menu.menuOptions !== null) {
+ return menu.menuOptions;
+ }
+
+ var result = {};
+ result['cutItem'] = menuOptions['cutItem'];
+ result['copyItem'] = menuOptions['copyItem'];
+ if (menu.app.pasteAllowed && menu.model.attributes.is_folderish) {
+ result['pasteItem'] = menuOptions['pasteItem'];
+ }
+ if (!menu.app.inQueryMode() && menu.options.canMove !== false) {
+ result['move-top'] = menuOptions['move-top'];
+ result['move-bottom'] = menuOptions['move-bottom'];
+ }
+ if (!menu.model.attributes.is_folderish && menu.app.setDefaultPageUrl) {
+ result['set-default-page'] = menuOptions['set-default-page'];
+ }
+ if (menu.model.attributes.is_folderish) {
+ result['selectAll'] = menuOptions['selectAll'];
+ }
+ if (menu.options.header) {
+ result['openItem'] = menuOptions['openItem'];
+ }
+ result['editItem'] = menuOptions['editItem'];
+ return result;
+ };
+
+ return ActionMenu;
+});
diff --git a/mockup/patterns/structure/js/actions.js b/mockup/patterns/structure/js/actions.js
new file mode 100644
index 000000000..be5f990ba
--- /dev/null
+++ b/mockup/patterns/structure/js/actions.js
@@ -0,0 +1,129 @@
+define([
+ 'jquery',
+ 'underscore',
+ 'backbone',
+ 'mockup-patterns-structure-url/js/models/result',
+ 'mockup-utils',
+ 'translate',
+], function($, _, Backbone, Result, utils, _t) {
+ 'use strict';
+
+ // use a more primative class than Backbone.Model?
+ var Actions = Backbone.Model.extend({
+ initialize: function(options) {
+ this.options = options;
+ this.app = options.app;
+ this.model = options.model;
+ this.selectedCollection = this.app.selectedCollection;
+ },
+ selectAll: function(e){
+ // This implementation is very specific to the default collection
+ // with the reliance on its queryParser and queryHelper. Custom
+ // collection (Backbone.Paginator.requestPager implementation)
+ // will have to come up with their own action for this.
+ e.preventDefault();
+ var self = this;
+ var page = 1;
+ var count = 0;
+ var getPage = function(){
+ self.app.loading.show();
+ $.ajax({
+ url: self.app.collection.url,
+ type: 'GET',
+ dataType: 'json',
+ data: {
+ query: self.app.collection.queryParser({
+ searchPath: self.model.attributes.path
+ }),
+ batch: JSON.stringify({
+ page: page,
+ size: 100
+ }),
+ attributes: JSON.stringify(
+ self.app.collection.queryHelper.options.attributes)
+ }
+ }).done(function(data){
+ var items = self.app.collection.parse(data, count);
+ count += items.length;
+ _.each(items, function(item){
+ self.app.selectedCollection.add(new Result(item));
+ });
+ page += 1;
+ if(data.total > count){
+ getPage();
+ }else{
+ self.app.loading.hide();
+ self.app.tableView.render();
+ }
+ });
+ };
+ getPage();
+ },
+
+ doAction: function(buttonName, successMsg, failMsg){
+ var self = this;
+ $.ajax({
+ url: self.app.buttons.get(buttonName).options.url,
+ data: {
+ selection: JSON.stringify([self.model.attributes.UID]),
+ folder: self.model.attributes.path,
+ _authenticator: utils.getAuthenticator()
+ },
+ dataType: 'json',
+ type: 'POST'
+ }).done(function(data){
+ if(data.status === 'success'){
+ self.app.setStatus(_t(successMsg + ' "' + self.model.attributes.Title + '"'));
+ self.app.collection.pager();
+ self.app.updateButtons();
+ }else{
+ self.app.setStatus(_t('Error ' + failMsg + ' "' + self.model.attributes.Title + '"'));
+ }
+ });
+ },
+
+ cutClicked: function(e) {
+ var self = this;
+ e.preventDefault();
+ self.doAction('cut', _t('Cut'), _t('cutting'));
+ },
+ copyClicked: function(e) {
+ var self = this;
+ e.preventDefault();
+ self.doAction('copy', _t('Copied'), _t('copying'));
+ },
+ pasteClicked: function(e) {
+ var self = this;
+ e.preventDefault();
+ self.doAction('paste', _t('Pasted into'), _t('Error pasting into'));
+ },
+ moveTopClicked: function(e) {
+ e.preventDefault();
+ this.app.moveItem(this.model.attributes.id, 'top');
+ },
+ moveBottomClicked: function(e) {
+ e.preventDefault();
+ this.app.moveItem(this.model.attributes.id, 'bottom');
+ },
+ setDefaultPageClicked: function(e) {
+ e.preventDefault();
+ var self = this;
+ $.ajax({
+ url: self.app.getAjaxUrl(self.app.setDefaultPageUrl),
+ type: 'POST',
+ data: {
+ '_authenticator': $('[name="_authenticator"]').val(),
+ 'id': this.model.attributes.id
+ },
+ success: function(data) {
+ self.app.ajaxSuccessResponse.apply(self.app, [data]);
+ },
+ error: function(data) {
+ self.app.ajaxErrorResponse.apply(self.app, [data]);
+ }
+ });
+ },
+ });
+
+ return Actions;
+});
diff --git a/mockup/patterns/structure/js/collections/result.js b/mockup/patterns/structure/js/collections/result.js
index 40cf6f625..74ca8fd23 100644
--- a/mockup/patterns/structure/js/collections/result.js
+++ b/mockup/patterns/structure/js/collections/result.js
@@ -2,20 +2,58 @@ define([
'underscore',
'backbone',
'mockup-patterns-structure-url/js/models/result',
+ 'mockup-utils',
'backbone.paginator'
-], function(_, Backbone, Result) {
+], function(_, Backbone, Result, Utils) {
'use strict';
var ResultCollection = Backbone.Paginator.requestPager.extend({
model: Result,
- queryHelper: null, // need to set
initialize: function(models, options) {
this.options = options;
+ this.view = options.view;
this.url = options.url;
- this.queryParser = options.queryParser;
- this.queryHelper = options.queryHelper;
+
+ this.queryHelper = Utils.QueryHelper(
+ $.extend(true, {}, this.view.options, {
+ attributes: this.view.options.queryHelperAttributes}));
+
+ this.queryParser = function(options) {
+ var self = this;
+ if(options === undefined){
+ options = {};
+ }
+ var term = null;
+ if (self.view.toolbar) {
+ term = self.view.toolbar.get('filter').term;
+ }
+ var sortOn = self.view.sort_on; // jshint ignore:line
+ var sortOrder = self.view.sort_order; // jshint ignore:line
+ if (!sortOn) {
+ sortOn = 'getObjPositionInParent';
+ }
+ return JSON.stringify({
+ criteria: self.queryHelper.getCriterias(term, $.extend({}, options, {
+ additionalCriterias: self.view.additionalCriterias
+ })),
+ sort_on: sortOn,
+ sort_order: sortOrder
+ });
+ }
+
+ // check and see if a hash is provided for initial path
+ if (window.location.hash.substring(0, 2) === '#/') {
+ this.queryHelper.currentPath = window.location.hash.substring(1);
+ }
+
Backbone.Paginator.requestPager.prototype.initialize.apply(this, [models, options]);
},
+ getCurrentPath: function() {
+ return this.queryHelper.getCurrentPath();
+ },
+ setCurrentPath: function(path) {
+ this.queryHelper.currentPath = path;
+ },
pager: function() {
this.trigger('pager');
Backbone.Paginator.requestPager.prototype.pager.apply(this, []);
@@ -38,6 +76,9 @@ define([
// how many items per page should be shown
perPage: 15
},
+ // server_api are query parameters passed directly (currently GET
+ // parameters). These are currently generated using following
+ // functions. Renamed to queryParams in Backbone.Paginator 2.0.
server_api: {
query: function() {
return this.queryParser();
diff --git a/mockup/patterns/structure/js/navigation.js b/mockup/patterns/structure/js/navigation.js
new file mode 100644
index 000000000..882a11e09
--- /dev/null
+++ b/mockup/patterns/structure/js/navigation.js
@@ -0,0 +1,50 @@
+define([
+ 'backbone',
+ 'mockup-utils',
+], function(Backbone, utils) {
+ 'use strict';
+
+ // use a more primative class than Backbone.Model?
+ var Navigation = Backbone.Model.extend({
+ initialize: function(options) {
+ this.options = options;
+ this.app = options.app;
+ this.model = options.model;
+ },
+
+ getSelectedBaseUrl: function() {
+ var self = this;
+ return self.model.attributes.getURL;
+ },
+ openUrl: function(url) {
+ var self = this;
+ var win = utils.getWindow();
+ var keyEvent = this.app.keyEvent;
+ if (keyEvent && keyEvent.ctrlKey) {
+ win.open(url);
+ } else {
+ win.location = url;
+ }
+ },
+ openClicked: function(e) {
+ e.preventDefault();
+ var self = this;
+ self.openUrl(self.getSelectedBaseUrl() + '/view');
+ },
+ editClicked: function(e) {
+ e.preventDefault();
+ var self = this;
+ self.openUrl(self.getSelectedBaseUrl() + '/edit');
+ },
+ folderClicked: function(e) {
+ e.preventDefault();
+ // handler for folder, go down path and show in contents window.
+ var self = this;
+ self.app.setCurrentPath(self.model.attributes.path);
+ // also switch to fix page in batch
+ self.app.collection.goTo(self.app.collection.information.firstPage);
+ },
+ });
+
+ return Navigation;
+});
diff --git a/mockup/patterns/structure/js/views/actionmenu.js b/mockup/patterns/structure/js/views/actionmenu.js
index e5ae54821..ac2b71847 100644
--- a/mockup/patterns/structure/js/views/actionmenu.js
+++ b/mockup/patterns/structure/js/views/actionmenu.js
@@ -5,176 +5,80 @@ define([
'mockup-ui-url/views/base',
'mockup-patterns-structure-url/js/models/result',
'mockup-utils',
+ 'mockup-patterns-structure-url/js/actions',
+ 'mockup-patterns-structure-url/js/actionmenu',
'text!mockup-patterns-structure-url/templates/actionmenu.xml',
'translate',
'bootstrap-dropdown'
-], function($, _, Backbone, BaseView, Result, utils, ActionMenuTemplate, _t) {
+], function($, _, Backbone, BaseView, Result, utils, Actions, ActionMenu,
+ ActionMenuTemplate, _t) {
'use strict';
- var ActionMenu = BaseView.extend({
+ var ActionMenuView = BaseView.extend({
className: 'btn-group actionmenu',
template: _.template(ActionMenuTemplate),
- events: {
- 'click .selectAll a': 'selectAll',
- 'click .cutItem a': 'cutClicked',
- 'click .copyItem a': 'copyClicked',
- 'click .pasteItem a': 'pasteClicked',
- 'click .move-top a': 'moveTopClicked',
- 'click .move-bottom a': 'moveBottomClicked',
- 'click .set-default-page a': 'setDefaultPageClicked',
- 'click .openItem a': 'openClicked',
- 'click .editItem a': 'editClicked'
- },
- initialize: function(options) {
- this.options = options;
- this.app = options.app;
- this.model = options.model;
- this.selectedCollection = this.app.selectedCollection;
- if (options.canMove === false){
- this.canMove = false;
- }else {
- this.canMove = true;
- }
- },
- selectAll: function(e){
- e.preventDefault();
+
+ // Static menu options
+ menuOptions: null,
+ // Dynamic menu options
+ menuGenerator: 'mockup-patterns-structure-url/js/actionmenu',
+
+ eventConstructor: function(definition) {
var self = this;
- var page = 1;
- var count = 0;
- var getPage = function(){
- self.app.loading.show();
- $.ajax({
- url: self.app.collection.url,
- type: 'GET',
- dataType: 'json',
- data: {
- query: self.app.collection.queryParser({
- searchPath: self.model.attributes.path
- }),
- batch: JSON.stringify({
- page: page,
- size: 100
- }),
- attributes: JSON.stringify(self.app.queryHelper.options.attributes)
- }
- }).done(function(data){
- var items = self.app.collection.parse(data, count);
- count += items.length;
- _.each(items, function(item){
- self.app.selectedCollection.add(new Result(item));
- });
- page += 1;
- if(data.total > count){
- getPage();
- }else{
- self.app.loading.hide();
- self.app.tableView.render();
- }
- });
+ var libName = definition[0],
+ method = definition[1];
+
+ if (!((typeof libName === 'string') && (typeof method === 'string'))) {
+ return false;
+ }
+
+ var doEvent = function(e) {
+ var libCls = require(libName);
+ var lib = new libCls(self)
+ return lib[method] && lib[method](e);
};
- getPage();
- },
- doAction: function(buttonName, successMsg, failMsg){
- var self = this;
- $.ajax({
- url: self.app.buttons.get(buttonName).options.url,
- data: {
- selection: JSON.stringify([self.model.attributes.UID]),
- folder: self.model.attributes.path,
- _authenticator: utils.getAuthenticator()
- },
- dataType: 'json',
- type: 'POST'
- }).done(function(data){
- if(data.status === 'success'){
- self.app.setStatus(_t(successMsg + ' "' + self.model.attributes.Title + '"'));
- self.app.collection.pager();
- self.app.updateButtons();
- }else{
- self.app.setStatus(_t('Error ' + failMsg + ' "' + self.model.attributes.Title + '"'));
- }
- });
- },
- cutClicked: function(e) {
- e.preventDefault();
- this.doAction('cut', _t('Cut'), _t('cutting'));
- },
- copyClicked: function(e) {
- e.preventDefault();
- this.doAction('copy', _t('Copied'), _t('copying'));
- },
- pasteClicked: function(e) {
- e.preventDefault();
- this.doAction('paste', _t('Pasted into'), _t('Error pasting into'));
+ return doEvent;
},
- moveTopClicked: function(e) {
- e.preventDefault();
- this.app.moveItem(this.model.attributes.id, 'top');
- },
- moveBottomClicked: function(e) {
- e.preventDefault();
- this.app.moveItem(this.model.attributes.id, 'bottom');
- },
- setDefaultPageClicked: function(e) {
- e.preventDefault();
+
+ events: function() {
var self = this;
- $.ajax({
- url: self.app.getAjaxUrl(self.app.setDefaultPageUrl),
- type: 'POST',
- data: {
- '_authenticator': $('[name="_authenticator"]').val(),
- 'id': this.model.attributes.id
- },
- success: function(data) {
- self.app.ajaxSuccessResponse.apply(self.app, [data]);
- },
- error: function(data) {
- self.app.ajaxErrorResponse.apply(self.app, [data]);
+ var result = {};
+ _.each(self.menuOptions, function(menuOption, idx) {
+ var e = self.eventConstructor(menuOption);
+ if (e) {
+ result['click .' + idx + ' a'] = e;
}
});
+ return result;
},
- getSelectedBaseUrl: function() {
- var self = this;
- return self.model.attributes.getURL;
- },
- getWindow: function() {
- var win = window;
- if (win.parent !== window) {
- win = win.parent;
- }
- return win;
- },
- openUrl: function(url) {
+
+ initialize: function(options) {
var self = this;
- var win = self.getWindow();
- var keyEvent = this.app.keyEvent;
- if (keyEvent && keyEvent.ctrlKey) {
- win.open(url);
- } else {
- win.location = url;
+ BaseView.prototype.initialize.apply(self, [options]);
+ self.options = options;
+ self.selectedCollection = self.app.selectedCollection;
+
+ // Then acquire the constructor method if specified, and
+ var menuGenerator = self.options.menuGenerator || self.menuGenerator;
+ if (menuGenerator) {
+ var menuGen = require(menuGenerator);
+ // override those options here. All definition done here so
+ // that self.events() will return the right things.
+ var menuOptions = menuGen(self);
+ if (typeof menuOptions === 'object') {
+ // Only assign this if we got the right basic type.
+ self.menuOptions = menuOptions;
+ // Should warn otherwise.
+ }
}
},
- openClicked: function(e) {
- e.preventDefault();
- var self = this;
- self.openUrl(self.getSelectedBaseUrl() + '/view');
- },
- editClicked: function(e) {
- e.preventDefault();
- var self = this;
- self.openUrl(self.getSelectedBaseUrl() + '/edit');
- },
render: function() {
var self = this;
self.$el.empty();
var data = this.model.toJSON();
- data.attributes = self.model.attributes;
- data.pasteAllowed = self.app.pasteAllowed;
- data.canSetDefaultPage = self.app.setDefaultPageUrl;
- data.inQueryMode = self.app.inQueryMode();
data.header = self.options.header || null;
- data.canMove = self.canMove;
+ data.menuOptions = self.menuOptions;
self.$el.html(self.template($.extend({
_t: _t,
@@ -191,5 +95,5 @@ define([
}
});
- return ActionMenu;
+ return ActionMenuView;
});
diff --git a/mockup/patterns/structure/js/views/app.js b/mockup/patterns/structure/js/views/app.js
index 94f78f9c9..31d186c8f 100644
--- a/mockup/patterns/structure/js/views/app.js
+++ b/mockup/patterns/structure/js/views/app.js
@@ -25,7 +25,7 @@ define([
TableView, SelectionWellView,
GenericPopover, RearrangeView, SelectionButtonView,
PagingView, ColumnsView, TextFilterView, UploadView,
- ResultCollection, SelectedCollection, utils, _t, logger) {
+ _ResultCollection, SelectedCollection, utils, _t, logger) {
'use strict';
var log = logger.getLogger('pat-structure');
@@ -44,6 +44,7 @@ define([
BaseView.prototype.initialize.apply(self, [options]);
self.loading = new utils.Loading();
self.loading.show();
+ self.pasteAllowed = $.cookie('__cp');
/* close popovers when clicking away */
$(document).click(function(e){
@@ -64,34 +65,17 @@ define([
}
});
+ var ResultCollection = require(options.collectionConstructor);
+
self.collection = new ResultCollection([], {
+ // Due to default implementation need to poke at things in here,
+ // view is passed.
+ view: self,
url: self.options.collectionUrl,
- queryParser: function(options) {
- if(options === undefined){
- options = {};
- }
- var term = null;
- if (self.toolbar) {
- term = self.toolbar.get('filter').term;
- }
- var sortOn = self['sort_on']; // jshint ignore:line
- if (!sortOn) {
- sortOn = 'getObjPositionInParent';
- }
- return JSON.stringify({
- criteria: self.queryHelper.getCriterias(term, $.extend({}, options, {
- additionalCriterias: self.additionalCriterias
- })),
- sort_on: sortOn,
- sort_order: self['sort_order'] // jshint ignore:line
- });
- },
- queryHelper: self.options.queryHelper
});
self.setAllCookieSettings();
- self.queryHelper = self.options.queryHelper;
self.selectedCollection = new SelectedCollection();
self.tableView = new TableView({app: self});
@@ -141,43 +125,82 @@ define([
self.loading.show();
self.updateButtons();
+ // the remaining calls are related to window.pushstate.
+ // abort if feature unavailable.
+ if (!(window.history && window.history.pushState)) {
+ return
+ }
+
+ // undo the flag set by popState to prevent the push state
+ // from being triggered here, and early abort out of the
+ // function to not execute the folowing pushState logic.
+ if (self.doNotPushState) {
+ self.doNotPushState = false;
+ return
+ }
+
+ var path = self.getCurrentPath();
+ if (path === '/'){
+ path = '';
+ }
/* maintain history here */
- if(self.options.urlStructure && window.history && window.history.pushState){
- if (!self.doNotPushState){
- var path = self.queryHelper.getCurrentPath();
- if(path === '/'){
- path = '';
- }
- var url = self.options.urlStructure.base + path + self.options.urlStructure.appended;
- window.history.pushState(null, null, url);
- $('body').trigger('structure-url-changed', path);
- }else{
- self.doNotPushState = false;
- }
+ if (self.options.pushStateUrl) {
+ // permit an extra slash in pattern, but strip that if there
+ // as path always will be prefixed with a `/`
+ var pushStateUrl = self.options.pushStateUrl.replace(
+ '/{path}', '{path}');
+ var url = pushStateUrl.replace('{path}', path);
+ window.history.pushState(null, null, url);
+ } else if (self.options.urlStructure) {
+ // fallback to urlStructure specification
+ var url = self.options.urlStructure.base + path + self.options.urlStructure.appended;
+ window.history.pushState(null, null, url);
+ }
+
+ if (self.options.traverseView) {
+ // flag specifies that the context view implements a traverse
+ // view (i.e. IPublishTraverse) and the path is a virtual path
+ // of some kind - use the base object instead for that by not
+ // specifying a path.
+ path = '';
+ // TODO figure out whether the following event after this is
+ // needed at all.
}
+ $('body').trigger('structure-url-changed', path);
+
});
- if (self.options.urlStructure && utils.featureSupport.history()){
+ if ((self.options.pushStateUrl || self.options.urlStructure)
+ && utils.featureSupport.history()){
$(window).bind('popstate', function () {
/* normalize this url first... */
- var url = window.location.href;
+ var win = utils.getWindow();
+ var url = win.location.href;
+ var base, appended;
if(url.indexOf('?') !== -1){
url = url.split('?')[0];
}
if(url.indexOf('#') !== -1){
url = url.split('#')[0];
}
+ if (self.options.pushStateUrl) {
+ var tmp = self.options.pushStateUrl.split('{path}');
+ base = tmp[0];
+ appended = tmp[1];
+ } else {
+ base = self.options.urlStructure.base;
+ appended = self.options.urlStructure.appended;
+ }
// take off the base url
- var path = url.substring(self.options.urlStructure.base.length);
- if(path.substring(path.length - self.options.urlStructure.appended.length) ===
- self.options.urlStructure.appended){
+ var path = url.substring(base.length);
+ if(path.substring(path.length - appended.length) === appended){
/* check that it ends with appended value */
- path = path.substring(0, path.length - self.options.urlStructure.appended.length);
+ path = path.substring(0, path.length - appended.length);
}
if(!path){
path = '/';
}
- self.queryHelper.currentPath = path;
+ self.setCurrentPath(path);
$('body').trigger('structure-url-changed', path);
// since this next call causes state to be pushed...
self.doNotPushState = true;
@@ -199,11 +222,13 @@ define([
self.buttons.disable();
}
- self.pasteAllowed = !!$.cookie('__cp');
- if (self.pasteAllowed) {
- self.buttons.get('paste').enable();
- }else{
- self.buttons.get('paste').disable();
+ if ('paste' in self.buttons) {
+ self.pasteAllowed = !!$.cookie('__cp');
+ if (self.pasteAllowed) {
+ self.buttons.get('paste').enable();
+ }else{
+ self.buttons.get('paste').disable();
+ }
}
},
inQueryMode: function() {
@@ -229,8 +254,14 @@ define([
});
return uids;
},
+ getCurrentPath: function() {
+ return this.collection.getCurrentPath();
+ },
+ setCurrentPath: function(path) {
+ this.collection.setCurrentPath(path);
+ },
getAjaxUrl: function(url) {
- return url.replace('{path}', this.options.queryHelper.getCurrentPath());
+ return url.replace('{path}', this.getCurrentPath());
},
buttonClickEvent: function(button) {
var self = this;
@@ -261,7 +292,7 @@ define([
}
data._authenticator = utils.getAuthenticator();
if (data.folder === undefined) {
- data.folder = self.options.queryHelper.getCurrentPath();
+ data.folder = self.getCurrentPath();
}
var url = self.getAjaxUrl(button.url);
@@ -467,7 +498,8 @@ define([
);
},
setAllCookieSettings: function() {
- this.activeColumns = this.getCookieSetting('activeColumns', this.activeColumns);
+ this.activeColumns = this.getCookieSetting(this['activeColumnsCookie'],
+ this.activeColumns);
var perPage = this.getCookieSetting('perPage', 15);
if(typeof(perPage) === 'string'){
perPage = parseInt(perPage);
diff --git a/mockup/patterns/structure/js/views/columns.js b/mockup/patterns/structure/js/views/columns.js
index 3784ac445..cd8c11fcc 100644
--- a/mockup/patterns/structure/js/views/columns.js
+++ b/mockup/patterns/structure/js/views/columns.js
@@ -63,7 +63,7 @@ define([
self.$('input:checked').each(function() {
self.app.activeColumns.push($(this).val());
});
- self.app.setCookieSetting('activeColumns', this.app.activeColumns);
+ self.app.setCookieSetting(self.app.activeColumnsCookie, this.app.activeColumns);
self.app.tableView.render();
}
});
diff --git a/mockup/patterns/structure/js/views/table.js b/mockup/patterns/structure/js/views/table.js
index 399bb1570..bdf124fe6 100644
--- a/mockup/patterns/structure/js/views/table.js
+++ b/mockup/patterns/structure/js/views/table.js
@@ -12,7 +12,7 @@ define([
'translate',
'bootstrap-alert'
], function($, _, Backbone, TableRowView, TableTemplate, BaseView, Sortable,
- Moment, Result, ActionMenu, _t) {
+ Moment, Result, ActionMenuView, _t) {
'use strict';
var TableView = BaseView.extend({
@@ -62,9 +62,11 @@ define([
if (self.selectedCollection.findWhere({UID: data.object.UID})){
$('input[type="checkbox"]', self.$breadcrumbs)[0].checked = true;
}
- self.folderMenu = new ActionMenu({
+ self.folderMenu = new ActionMenuView({
app: self.app,
model: self.folderModel,
+ menuOptions: self.app.menuOptions,
+ menuGenerator: self.app.menuGenerator,
header: _t('Actions on current folder'),
canMove: false
});
@@ -78,7 +80,7 @@ define([
self.$el.html(self.template({
_t: _t,
pathParts: _.filter(
- self.app.queryHelper.getCurrentPath().split('/').slice(1),
+ self.app.getCurrentPath().split('/').slice(1),
function(val) {
return val.length > 0;
}
@@ -105,7 +107,11 @@ define([
selector: '.ModificationDate,.EffectiveDate,.CreationDate,.ExpirationDate',
format: self.options.app.momentFormat
});
- self.addReordering();
+
+ if (self.app.options.moveUrl) {
+ self.addReordering();
+ }
+
self.storeOrder();
return this;
},
@@ -124,7 +130,7 @@ define([
}
});
path += $el.attr('data-path');
- this.app.queryHelper.currentPath = path;
+ this.app.setCurrentPath(path);
this.collection.pager();
},
selectFolder: function(e) {
diff --git a/mockup/patterns/structure/js/views/tablerow.js b/mockup/patterns/structure/js/views/tablerow.js
index 896d728da..5cda20e5b 100644
--- a/mockup/patterns/structure/js/views/tablerow.js
+++ b/mockup/patterns/structure/js/views/tablerow.js
@@ -2,11 +2,12 @@ define([
'jquery',
'underscore',
'backbone',
+ 'mockup-patterns-structure-url/js/navigation',
'mockup-patterns-structure-url/js/views/actionmenu',
'text!mockup-patterns-structure-url/templates/tablerow.xml',
'mockup-utils',
'translate'
-], function($, _, Backbone, ActionMenu, TableRowTemplate, utils, _t) {
+], function($, _, Backbone, Nav, ActionMenuView, TableRowTemplate, utils, _t) {
'use strict';
var TableRowView = Backbone.View.extend({
@@ -30,9 +31,21 @@ define([
if (this.selectedCollection.findWhere({UID: data.UID})) {
data.selected = true;
}
+ if (!data.viewURL) {
+ // XXX
+ // This is for the new window link. There should also be a
+ // separate one for the default link and it shouldn't require a
+ // javascript function to append '/view' on the default click.
+ // Need actual documentation reference for this and also support
+ // from the vocabulary that generates the data for the default
+ // portal_contents view.
+ data.viewURL = data.getURL + '/view';
+ }
data.attributes = self.model.attributes;
data.activeColumns = self.app.activeColumns;
data.availableColumns = self.app.availableColumns;
+ data.portal_type = data.portal_type ? data.portal_type : '';
+ data.contenttype = data.portal_type.toLowerCase().replace(/\.| /g, '-');
data._authenticator = utils.getAuthenticator();
data._t = _t;
self.$el.html(self.template(data));
@@ -49,29 +62,44 @@ define([
self.el.model = this.model;
- self.menu = new ActionMenu({
+ var canMove = (!(!self.app.options.moveUrl));
+
+ self.menu = new ActionMenuView({
app: self.app,
- model: self.model
+ model: self.model,
+ menuOptions: self.app.menuOptions,
+ menuGenerator: self.app.menuGenerator,
+ canMove: canMove
});
$('.actionmenu-container', self.$el).append(self.menu.render().el);
return this;
},
itemClicked: function(e) {
- e.preventDefault();
/* check if this should just be opened in new window */
+ var self = this;
var keyEvent = this.app.keyEvent;
- if (keyEvent && keyEvent.ctrlKey) {
- this.menu.openClicked(e);
- } else if (this.model.attributes['is_folderish']) { // jshint ignore:line
- // it's a folder, go down path and show in contents window.
- this.app.queryHelper.currentPath = this.model.attributes.path;
- // also switch to fix page in batch
- var collection = this.app.collection;
- collection.goTo(collection.information.firstPage);
+ var key;
+ // Resolve the correct handler based on these keys.
+ // Default handlers live in ../navigation.js (bound to Nav)
+ if (keyEvent && keyEvent.ctrlKey ||
+ !(this.model.attributes['is_folderish'])) {
+ // middle/ctrl-click or not a folder content
+ key = 'other'; // default Nav.openClicked
} else {
- this.menu.openClicked(e);
+ key = 'folder'; // default Nav.folderClicked
+ }
+ var definition = self.app.options.tableRowItemAction[key] || [];
+ // a bit of a duplicate from actionmenu.js, but this is calling
+ // directly.
+ var libName = definition[0],
+ method = definition[1];
+ if (!((typeof libName === 'string') && (typeof key === 'string'))) {
+ return null;
}
+ var clsLib = require(libName);
+ var lib = new clsLib(self);
+ return lib[method] && lib[method](e);
},
itemSelected: function() {
var checkbox = this.$('input')[0];
diff --git a/mockup/patterns/structure/js/views/upload.js b/mockup/patterns/structure/js/views/upload.js
index f9c5921e9..1ecb03f5f 100644
--- a/mockup/patterns/structure/js/views/upload.js
+++ b/mockup/patterns/structure/js/views/upload.js
@@ -34,7 +34,7 @@ define([
options.relatedItems = {
vocabularyUrl: self.app.options.vocabularyUrl
};
- options.currentPath = self.app.options.queryHelper.getCurrentPath();
+ options.currentPath = self.app.getCurrentPath();
self.upload = new Upload(self.$('.uploadify-me').addClass('pat-upload'), options);
return this;
},
@@ -46,7 +46,7 @@ define([
if (!this.opened) {
return;
}
- var currentPath = self.app.queryHelper.getCurrentPath();
+ var currentPath = self.app.getCurrentPath();
var relatedItems = self.upload.relatedItems;
if (self.currentPathData && relatedItems && currentPath !== self.upload.currentPath){
if (currentPath === '/'){
diff --git a/mockup/patterns/structure/pattern.js b/mockup/patterns/structure/pattern.js
index acdf0f35a..d4e47316a 100644
--- a/mockup/patterns/structure/pattern.js
+++ b/mockup/patterns/structure/pattern.js
@@ -48,19 +48,41 @@ define([
indexOptionsUrl: null, // for querystring widget
contextInfoUrl: null, // for add new dropdown and other info
setDefaultPageUrl: null,
+ menuOptions: null, // default action menu options per item.
+ // default menu generator
+ menuGenerator: 'mockup-patterns-structure-url/js/actionmenu',
backdropSelector: '.plone-modal', // Element upon which to apply backdrops used for popovers
- attributes: [
+
+ activeColumnsCookie: 'activeColumns',
+
+ /*
+ As the options operate on a merging basis per new attribute
+ (key/value pairs) on the option Object in a recursive fashion,
+ array items are also treated as Objects so that custom options
+ are replaced starting from index 0 up to the length of the
+ array. In the case of buttons, custom buttons are simply
+ replaced starting from the first one. The following defines the
+ customized attributes that should be replaced wholesale, with
+ the default version prefixed with `_default_`.
+ */
+
+ attributes: null,
+ _default_attributes: [
'UID', 'Title', 'portal_type', 'path', 'review_state',
'ModificationDate', 'EffectiveDate', 'CreationDate',
'is_folderish', 'Subject', 'getURL', 'id', 'exclude_from_nav',
'getObjSize', 'last_comment_date', 'total_comments','getIcon'
],
- activeColumns: [
+
+ activeColumns: null,
+ _default_activeColumns: [
'ModificationDate',
'EffectiveDate',
'review_state'
],
- availableColumns: {
+
+ availableColumns: null,
+ _default_availableColumns: {
'id': 'ID',
'ModificationDate': 'Last modified',
'EffectiveDate': 'Published',
@@ -75,6 +97,19 @@ define([
'last_comment_date': 'Last comment date',
'total_comments': 'Total comments'
},
+
+ // action triggered for the primary link for each table row.
+ tableRowItemAction: null,
+ _default_tableRowItemAction: {
+ folder: [
+ 'mockup-patterns-structure-url/js/navigation', 'folderClicked'],
+ other: [
+ 'mockup-patterns-structure-url/js/navigation', 'openClicked']
+ },
+
+ collectionConstructor:
+ 'mockup-patterns-structure-url/js/collections/result',
+
momentFormat: 'relative',
rearrange: {
properties: {
@@ -85,8 +120,9 @@ define([
},
basePath: '/',
moveUrl: null,
- buttons: [],
- demoButtons: [{
+
+ buttons: null,
+ _default_buttons: [{
title: 'Cut',
url: '/cut'
},{
@@ -113,28 +149,48 @@ define([
title: 'Rename',
url: '/rename'
}],
+
upload: {
uploadMultiple: true,
showTitle: true
}
+
},
init: function() {
var self = this;
- if(self.options.buttons.length === 0){
- /* XXX I know this is wonky... but this prevents
- weird option merging issues */
- self.options.buttons = self.options.demoButtons;
- }
+
+ /*
+ This part replaces the undefined (null) values in the user
+ modifiable attributes with the default values.
+
+ May want to consider moving the _default_* values out of the
+ options object.
+ */
+ var replaceDefaults = [
+ 'attributes', 'activeColumns', 'availableColumns', 'buttons'];
+ _.each(replaceDefaults, function(idx) {
+ if (self.options[idx] === null) {
+ self.options[idx] = self.options['_default_' + idx];
+ }
+ });
+
+ var mergeDefaults = ['tableRowItemAction'];
+ _.each(mergeDefaults, function(idx) {
+ var old = self.options[idx];
+ self.options[idx] = $.extend(
+ false, self.options['_default_' + idx], old
+ );
+ });
+
self.browsing = true; // so all queries will be correct with QueryHelper
self.options.collectionUrl = self.options.vocabularyUrl;
- self.options.queryHelper = new utils.QueryHelper(
- $.extend(true, {}, self.options, {pattern: self}));
+ self.options.pattern = self;
- // check and see if a hash is provided for initial path
- if(window.location.hash.substring(0, 2) === '#/'){
- self.options.queryHelper.currentPath = window.location.hash.substring(1);
- }
- delete self.options.attributes; // not compatible with backbone
+ // the ``attributes`` options key is not compatible with backbone,
+ // but queryHelper that will be constructed by the default
+ // ResultCollection will expect this to be passed into it.
+ self.options.queryHelperAttributes = self.options.attributes;
+ delete self.options.attributes;
self.view = new AppView(self.options);
self.$el.append(self.view.render().$el);
diff --git a/mockup/patterns/structure/templates/actionmenu.xml b/mockup/patterns/structure/templates/actionmenu.xml
index a0c30abfd..830264ff0 100644
--- a/mockup/patterns/structure/templates/actionmenu.xml
+++ b/mockup/patterns/structure/templates/actionmenu.xml
@@ -8,24 +8,10 @@
<% } %>
- <%- _t("Cut") %>
- <%- _t("Copy") %>
- <% if(pasteAllowed && attributes.is_folderish){ %>
- <%- _t("Paste") %>
- <% } %>
- <% if(!inQueryMode && canMove){ %>
- <%- _t("Move to top of folder") %>
- <%- _t("Move to bottom of folder") %>
- <% } %>
- <% if(!attributes.is_folderish && canSetDefaultPage){ %>
- <%- _t("Set as default page") %>
- <% } %>
- <% if(attributes.is_folderish){ %>
- <%- _t("Select all contained items") %>
- <% } %>
- <% if(header) { %>
- <%- _t("Open") %>
- <% } %>
- <%- _t("Edit") %>
+
+ <% _.each(menuOptions, function(menuOption, idx){ %>
+ <%- _t(menuOption[3]) %>
+ <% }); %>
+
diff --git a/mockup/patterns/structure/templates/tablerow.xml b/mockup/patterns/structure/templates/tablerow.xml
index 896761af0..29bc4a0e1 100644
--- a/mockup/patterns/structure/templates/tablerow.xml
+++ b/mockup/patterns/structure/templates/tablerow.xml
@@ -1,13 +1,14 @@
checked="checked" <% } %>/> |
-
<%- Title %>
<% if(attributes["getIcon"] ){ %> <% } %>
-
+
diff --git a/mockup/tests/pattern-structure-test.js b/mockup/tests/pattern-structure-test.js
index f5eb5e3c9..3154cb70c 100644
--- a/mockup/tests/pattern-structure-test.js
+++ b/mockup/tests/pattern-structure-test.js
@@ -3,13 +3,31 @@ define([
'jquery',
'pat-registry',
'mockup-patterns-structure',
+ 'mockup-patterns-structure-url/js/views/actionmenu',
+ 'mockup-patterns-structure-url/js/views/app',
+ 'mockup-patterns-structure-url/js/models/result',
+ 'mockup-utils',
'sinon',
-], function(expect, $, registry, Structure, sinon) {
+], function(expect, $, registry, Structure, ActionMenuView, AppView, Result,
+ utils, sinon) {
'use strict';
window.mocha.setup('bdd');
$.fx.off = true;
+ var structureUrlChangedPath = '';
+ var dummyWindow = {};
+ var history = {
+ 'pushState': function(data, title, url) {
+ history.pushed = {
+ data: data,
+ title: title,
+ url: url
+ };
+ }
+ };
+ dummyWindow.history = history;
+
function getQueryVariable(url, variable) {
var query = url.split('?')[1];
if (query === undefined) {
@@ -28,29 +46,360 @@ define([
var extraDataJsonItem = null;
+ /* ==========================
+ TEST: AppView constructor internal attribute/object correctness
+ ========================== */
+ describe('AppView internals correctness', function() {
+
+ it('AppView collection queryHelper attribute', function() {
+ /*
+ Since the test and dummy data provided later directly provides
+ that without actually consuming the query parameters that are
+ generated by the QueryHelper instance internal to this pattern,
+ it should be tested here.
+
+ This is to ensure that if its construction is later changed
+ again it should at least trigger some failure and ensure that
+ the "fixed" version will continue to generate the correct
+ query parameters..
+ */
+
+ var app = new AppView({
+ // dummy pattern, extracted/referenced by QueryHelper
+ 'pattern': {
+ 'browsing': true,
+ 'basePath': '/'
+ },
+ // the pattern accepts this as `attributes` but backbone doesn't
+ // accept this as a valid parameter, it must be renamed to be
+ // reused. The default ResultCollection implementation will
+ // then pass this back again as the `attributes` parameter to
+ // construct the internal QueryHelper instance that it owns.
+ 'queryHelperAttributes': ['foo', 'bar'],
+
+ 'buttons': [{'title': 'Cut', 'url': '/cut'}],
+ 'activeColumns': [],
+ 'availableColumns': [],
+ 'indexOptionsUrl': '',
+ 'setDefaultPageUrl': '',
+ 'url': 'http://localhost:8081/vocab',
+ 'collectionConstructor':
+ 'mockup-patterns-structure-url/js/collections/result'
+ });
+
+ expect(app.collection.queryHelper.options.attributes).to.eql(
+ ['foo', 'bar']);
+
+ expect(JSON.parse(app.collection.queryParser())).to.eql({
+ "criteria": [{
+ "i":"path",
+ "o":"plone.app.querystring.operation.string.path",
+ "v":"/::1"
+ }],
+ "sort_on":"getObjPositionInParent",
+ "sort_order":"ascending"
+ });
+
+ });
+ });
+
+
+ /* ==========================
+ TEST: Per Item Action Buttons
+ ========================== */
+ describe('Per Item Action Buttons', function() {
+ beforeEach(function() {
+ this.server = sinon.fakeServer.create();
+ this.server.autoRespond = true;
+
+ this.server.respondWith('POST', '/cut', function (xhr, id) {
+ xhr.respond(200, { 'Content-Type': 'application/json' },
+ JSON.stringify({
+ status: 'success',
+ msg: 'cut'
+ }));
+ });
+
+ this.clock = sinon.useFakeTimers();
+
+ this.$el = $('').appendTo('body');
+
+ this.app = new AppView({
+ // XXX ActionButton need this lookup directly.
+ 'buttons': [{'title': 'Cut', 'url': '/cut'}],
+
+ 'activeColumns': [],
+ 'availableColumns': [],
+ 'indexOptionsUrl': '',
+ 'setDefaultPageUrl': '',
+ 'collectionConstructor':
+ 'mockup-patterns-structure-url/js/collections/result',
+ });
+ this.app.render();
+ });
+
+ afterEach(function() {
+ this.clock.restore();
+ this.server.restore();
+ requirejs.undef('dummytestactions');
+ requirejs.undef('dummytestactionmenu');
+ });
+
+ it('basic action menu rendering', function() {
+ var model = new Result({
+ "Title": "Dummy Object",
+ "is_folderish": true,
+ "review_state": "published"
+ });
+
+ var menu = new ActionMenuView({
+ app: this.app,
+ model: model,
+ header: 'Menu Header'
+ });
+
+ var el = menu.render().el;
+
+ expect($('li.dropdown-header', el).text()).to.equal('Menu Header');
+ expect($('li a', el).length).to.equal(7);
+ expect($($('li a', el)[0]).text()).to.equal('Cut');
+
+ $('.cutItem a', el).click();
+ this.clock.tick(500);
+
+ expect(this.app.$('.status').text()).to.equal('Cut "Dummy Object"');
+
+ });
+
+ it('custom action menu items', function() {
+ var model = new Result({
+ "Title": "Dummy Object",
+ "is_folderish": true,
+ "review_state": "published"
+ });
+
+ var menu = new ActionMenuView({
+ app: this.app,
+ model: model,
+ menuOptions: {
+ 'cutItem': [
+ 'mockup-patterns-structure-url/js/actions',
+ 'cutClicked',
+ '#',
+ 'Cut',
+ ],
+ },
+ });
+
+ var el = menu.render().el;
+ expect($('li a', el).length).to.equal(1);
+ expect($($('li a', el)[0]).text()).to.equal('Cut');
+
+ $('.cutItem a', el).click();
+ this.clock.tick(500);
+ expect(this.app.$('.status').text()).to.equal('Cut "Dummy Object"');
+
+ });
+
+ it('custom action menu items and actions.', function() {
+ // Define a custom dummy "module"
+ define('dummytestactions', ['backbone'], function(Backbone) {
+ var Actions = Backbone.Model.extend({
+ initialize: function(options) {
+ this.options = options;
+ this.app = options.app;
+ },
+ foobarClicked: function(e) {
+ var self = this;
+ self.app.setStatus('Status: foobar clicked');
+ }
+ });
+ return Actions;
+ });
+ // use it to make it available synchronously.
+ require(['dummytestactions'], function(){});
+ this.clock.tick(500);
+
+ var model = new Result({
+ "is_folderish": true,
+ "review_state": "published"
+ });
+
+ // Make use if that dummy in here.
+ var menu = new ActionMenuView({
+ app: this.app,
+ model: model,
+ menuOptions: {
+ 'foobar': [
+ 'dummytestactions',
+ 'foobarClicked',
+ '#',
+ 'Foo Bar',
+ ],
+ },
+ });
+
+ var el = menu.render().el;
+ expect($('li a', el).length).to.equal(1);
+ expect($($('li a', el)[0]).text()).to.equal('Foo Bar');
+
+ $('.foobar a', el).click();
+ this.clock.tick(500);
+ expect(this.app.$('.status').text()).to.equal('Status: foobar clicked');
+ });
+
+ it('custom action menu actions missing.', function() {
+ // Define a custom dummy "module"
+ define('dummytestactions', ['backbone'], function(Backbone) {
+ var Actions = Backbone.Model.extend({
+ initialize: function(options) {
+ this.options = options;
+ this.app = options.app;
+ },
+ barbazClicked: function(e) {
+ var self = this;
+ self.app.setStatus('Status: barbaz clicked');
+ }
+ });
+ return Actions;
+ });
+
+ // use it to make it available synchronously.
+ require(['dummytestactions'], function(){});
+ this.clock.tick(500);
+
+ var model = new Result({
+ "is_folderish": true,
+ "review_state": "published"
+ });
+
+ // Make use if that dummy in here.
+ var menu = new ActionMenuView({
+ app: this.app,
+ model: model,
+ menuOptions: {
+ 'foobar': [
+ 'dummytestactions',
+ 'foobarClicked',
+ '#',
+ 'Foo Bar',
+ ],
+ 'barbaz': [
+ 'dummytestactions',
+ 'barbazClicked',
+ '#',
+ 'Bar Baz',
+ ],
+ },
+ });
+
+ // Broken/missing action
+ var el = menu.render().el;
+ $('.foobar a', el).click();
+ this.clock.tick(500);
+ expect(this.app.$('.status').text().trim()).to.equal('');
+ });
+
+ it('custom action menu via generator.', function() {
+ // Define a custom dummy "module"
+ define('dummytestactions', ['backbone'], function(Backbone) {
+ var Actions = Backbone.Model.extend({
+ initialize: function(options) {
+ this.options = options;
+ this.app = options.app;
+ },
+ barbazClicked: function(e) {
+ var self = this;
+ self.app.setStatus('Status: barbaz clicked');
+ }
+ });
+ return Actions;
+ });
+
+ define('dummytestactionmenu', ['backbone'], function(Backbone) {
+ var ActionMenu = function(menu) {
+ return {
+ 'barbaz': [
+ 'dummytestactions',
+ 'barbazClicked',
+ '#',
+ 'Bar Baz'
+ ]
+ };
+ };
+ return ActionMenu;
+ });
+ // use them both to make it available synchronously.
+ require(['dummytestactions'], function(){});
+ require(['dummytestactionmenu'], function(){});
+ this.clock.tick(500);
+
+ var model = new Result({
+ "is_folderish": true,
+ "review_state": "published"
+ });
+
+ // Make use if that dummy in here.
+ var menu = new ActionMenuView({
+ app: this.app,
+ model: model,
+ menuGenerator: 'dummytestactionmenu'
+ });
+
+ // Broken/missing action
+ var el = menu.render().el;
+ $('.barbaz a', el).click();
+ this.clock.tick(500);
+ expect(this.app.$('.status').text().trim()).to.equal(
+ 'Status: barbaz clicked');
+ });
+
+ });
+
+
/* ==========================
TEST: Structure
========================== */
describe('Structure', function() {
beforeEach(function() {
// clear cookie setting
+ $.removeCookie('__cp');
$.removeCookie('_fc_perPage');
+ $.removeCookie('_fc_activeColumns');
+ $.removeCookie('_fc_activeColumnsCustom');
- this.$el = $('' +
- '' +
- ' ').appendTo('body');
+ var structure = {
+ "vocabularyUrl": "/data.json",
+ "uploadUrl": "/upload",
+ "moveUrl": "/moveitem",
+ "indexOptionsUrl": "/tests/json/queryStringCriteria.json",
+ "contextInfoUrl": "{path}/contextInfo",
+ "setDefaultPageUrl": "/setDefaultPage",
+ "urlStructure": {
+ "base": "http://localhost:8081",
+ "appended": "/folder_contents"
+ }
+ };
+
+ this.$el = $('').attr(
+ 'data-pat-structure', JSON.stringify(structure)).appendTo('body');
this.server = sinon.fakeServer.create();
this.server.autoRespond = true;
+ $('body').off('structure-url-changed').on('structure-url-changed',
+ function (e, path) {
+ structureUrlChangedPath = path;
+ }
+ );
+
this.server.respondWith('GET', /data.json/, function (xhr, id) {
var batch = JSON.parse(getQueryVariable(xhr.url, 'batch'));
+ var query = JSON.parse(getQueryVariable(xhr.url, 'query'));
+ var path = query.criteria[0].v.split(':')[0];
+ if (path === '/') {
+ path = '';
+ }
var start = 0;
var end = 15;
if (batch) {
@@ -59,9 +408,9 @@ define([
}
var items = [];
items.push({
- UID: '123sdfasdfFolder',
- getURL: 'http://localhost:8081/folder',
- path: '/folder',
+ UID: '123sdfasdf' + path + 'Folder',
+ getURL: 'http://localhost:8081' + path + '/folder',
+ path: path + '/folder',
portal_type: 'Folder',
Description: 'folder',
Title: 'Folder',
@@ -72,9 +421,9 @@ define([
});
for (var i = start; i < end; i = i + 1) {
items.push({
- UID: '123sdfasdf' + i,
- getURL: 'http://localhost:8081/item' + i,
- path: '/item' + i,
+ UID: '123sdfasdf' + path + i,
+ getURL: 'http://localhost:8081' + path + '/item' + i,
+ path: path + '/item' + i,
portal_type: 'Document ' + i,
Description: 'document',
Title: 'Document ' + i,
@@ -106,6 +455,18 @@ define([
msg: 'pasted'
}));
});
+ this.server.respondWith('POST', '/moveitem', function (xhr, id) {
+ xhr.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({
+ status: 'success',
+ msg: 'moved ' + xhr.requestBody
+ }));
+ });
+ this.server.respondWith('POST', '/setDefaultPage', function (xhr, id) {
+ xhr.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({
+ status: 'success',
+ msg: 'defaulted'
+ }));
+ });
this.server.respondWith('GET', /contextInfo/, function (xhr, id) {
var data = {
addButtons: [{
@@ -135,16 +496,32 @@ define([
});
this.clock = sinon.useFakeTimers();
+
+ sinon.stub(utils, 'getWindow', function() {
+ return dummyWindow;
+ });
+
+ this.sandbox = sinon.sandbox.create();
+ this.sandbox.stub(window, 'history', history);
});
afterEach(function() {
+ // XXX QueryHelper behaves like a singleton as it pins self
+ // reference to the singleton instance of Utils within the
+ // requirejs framework, so its variables such as currentPath are
+ // persisted. Reset that here like so:
+ utils.QueryHelper({}).currentPath = '/';
extraDataJsonItem = null;
this.server.restore();
this.clock.restore();
+ this.sandbox.restore();
+ $('body').html('');
+ utils.getWindow.restore();
});
it('initialize', function() {
registry.scan(this.$el);
+ // moveUrl provided, can get to this via order-support.
expect(this.$el.find('.order-support > table').size()).to.equal(1);
});
@@ -275,7 +652,8 @@ define([
$item.trigger('change');
this.clock.tick(1000);
expect(this.$el.find('#btn-selected-items').html()).to.contain('16');
-
+ expect($('table tbody .selection input:checked', this.$el).length
+ ).to.equal(16);
});
it('test unselect all', function() {
@@ -286,10 +664,15 @@ define([
$item.trigger('change');
this.clock.tick(1000);
expect(this.$el.find('#btn-selected-items').html()).to.contain('16');
+ expect($('table tbody .selection input:checked', this.$el).length
+ ).to.equal(16);
+
$item[0].checked = false;
$item.trigger('change');
this.clock.tick(1000);
expect(this.$el.find('#btn-selected-items').html()).to.contain('0');
+ expect($('table tbody .selection input:checked', this.$el).length
+ ).to.equal(0);
});
it('test current folder buttons do not show on root', function() {
@@ -321,5 +704,1586 @@ define([
expect(this.$el.find('#btn-selected-items').html()).to.contain('1');
});
+ it('test displayed content', function() {
+ registry.scan(this.$el);
+ this.clock.tick(500);
+
+ var $content_row = this.$el.find('table tbody tr').eq(0);
+ expect($content_row.find('td').length).to.equal(6);
+ expect($content_row.find('td').eq(1).text().trim()).to.equal('Folder');
+ expect($content_row.find('td').eq(2).text().trim()).to.equal('');
+ expect($content_row.find('td').eq(3).text().trim()).to.equal('');
+ expect($content_row.find('td').eq(4).text().trim()).to.equal('published');
+ expect($content_row.find('td .icon-group-right a').attr('href')
+ ).to.equal('http://localhost:8081/folder/view');
+
+ var $content_row1 = this.$el.find('table tbody tr').eq(1);
+ expect($content_row1.find('td').eq(1).text().trim()).to.equal(
+ 'Document 0');
+ expect($content_row1.find('td .icon-group-right a').attr('href')
+ ).to.equal('http://localhost:8081/item0/view');
+ });
+
+ it('test select all contained item action', function() {
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+
+ // Since the top level view doesn't currently provide 'Actions
+ // on current folder' action menu, go down one level.
+ var $item = this.$el.find('.itemRow').eq(0);
+ $('.title a', $item).trigger('click');
+ this.clock.tick(1000);
+
+ var menu = $('.fc-breadcrumbs-container .actionmenu', this.$el);
+ var options = $('ul li a', menu);
+ expect(options.length).to.equal(5);
+
+ var selectAll = $('.selectAll a', menu);
+ expect(selectAll.text()).to.eql('Select all contained items');
+ selectAll.trigger('click');
+ this.clock.tick(1000);
+ expect($('table tbody .selection input:checked', this.$el).length
+ ).to.equal(16);
+ expect(this.$el.find('#btn-selected-items').html()).to.contain('101');
+ });
+
+ it('test select displayed columns', function() {
+ registry.scan(this.$el);
+ this.clock.tick(500);
+ var $row = this.$el.find('table thead tr').eq(1);
+ expect($row.find('th').length).to.equal(6);
+ expect($row.find('th').eq(1).text()).to.equal('Title');
+ expect($row.find('th').eq(2).text()).to.equal('Last modified');
+ expect($row.find('th').eq(3).text()).to.equal('Published');
+ expect($row.find('th').eq(4).text()).to.equal('Review state');
+ expect($row.find('th').eq(5).text()).to.equal('Actions');
+
+ expect($.cookie('_fc_activeColumns')).to.be(undefined);
+
+ this.$el.find('#btn-attribute-columns').trigger('click');
+ this.clock.tick(500);
+
+ var $checkbox = this.$el.find(
+ '.attribute-columns input[value="getObjSize"]');
+ $checkbox[0].checked = true;
+ $checkbox.trigger('change');
+ this.clock.tick(500);
+
+ var $popover = this.$el.find('.popover.attribute-columns');
+ expect($popover.find('button').text()).to.equal('Save');
+ $popover.find('button').trigger('click');
+ this.clock.tick(500);
+
+ $row = this.$el.find('table thead tr').eq(1);
+ expect($row.find('th').length).to.equal(7);
+ expect($row.find('th').eq(5).text()).to.equal('Object Size');
+ expect($row.find('th').eq(6).text()).to.equal('Actions');
+ expect($.parseJSON($.cookie('_fc_activeColumns')).value).to.eql(
+ ["ModificationDate", "EffectiveDate", "review_state", "getObjSize"]);
+
+ $checkbox[0].checked = false;
+ $checkbox.trigger('change');
+ $popover.find('button').trigger('click');
+ this.clock.tick(500);
+
+ $row = this.$el.find('table thead tr').eq(1);
+ expect($row.find('th').length).to.equal(6);
+ expect($.parseJSON($.cookie('_fc_activeColumns')).value).to.eql(
+ ["ModificationDate", "EffectiveDate", "review_state"]);
+
+ });
+
+ it('test main buttons count', function() {
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+ var buttons = this.$el.find('#btngroup-mainbuttons a');
+ expect(buttons.length).to.equal(8);
+ });
+
+ it('test itemRow default actionmenu folder', function() {
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+ // folder
+ var folder = this.$el.find('.itemRow').eq(0);
+ expect(folder.data().id).to.equal('folder');
+ expect($('.actionmenu ul li a', folder).length).to.equal(6);
+ // no pasting (see next test
+ expect($('.actionmenu ul li.pasteItem', folder).length).to.equal(0);
+ // no set default page
+ expect($('.actionmenu ul li.set-default-page a', folder).length
+ ).to.equal(0);
+ // can select all
+ expect($('.actionmenu ul li.selectAll', folder).text()).to.equal(
+ 'Select all contained items');
+ });
+
+ it('test itemRow default actionmenu item', function() {
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+
+ var item = this.$el.find('.itemRow').eq(10);
+ expect(item.data().id).to.equal('item9');
+ expect($('.actionmenu ul li a', item).length).to.equal(6);
+ // cannot select all
+ expect($('.actionmenu ul li.selectAll a', item).length).to.equal(0);
+ // can set default page
+ expect($('.actionmenu ul li.set-default-page', item).text()).to.equal(
+ 'Set as default page');
+ $('.actionmenu ul li.set-default-page a', item).click();
+ this.clock.tick(1000);
+ expect(this.$el.find('.order-support .status').html()).to.contain(
+ 'defaulted');
+ });
+
+ it('test itemRow actionmenu paste click', function() {
+ // item pending to be pasted
+ $.cookie('__cp', 'dummy');
+ this.clock.tick(1000);
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+ // top item
+ var item0 = this.$el.find('.itemRow').eq(0);
+ expect(item0.data().id).to.equal('folder');
+ expect($('.actionmenu ul li a', item0).length).to.equal(7);
+ expect($('.actionmenu ul li.pasteItem', item0).text()).to.equal('Paste');
+ $('.actionmenu ul li.pasteItem a', item0).click();
+ this.clock.tick(1000);
+ expect(this.$el.find('.order-support .status').html()).to.contain(
+ 'Pasted into "Folder"');
+ });
+
+ it('test itemRow actionmenu move-top click', function() {
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+ // top item
+ var item0 = this.$el.find('.itemRow').eq(0);
+ expect(item0.data().id).to.equal('folder');
+ var item10 = this.$el.find('.itemRow').eq(10);
+ expect(item10.data().id).to.equal('item9');
+
+ expect($('.actionmenu ul li.move-top', item10).text()).to.equal(
+ 'Move to top of folder');
+ $('.actionmenu ul li.move-top a', item10).trigger('click');
+ this.clock.tick(1000);
+
+ expect(this.$el.find('.order-support .status').html()).to.contain(
+ 'moved');
+ expect(this.$el.find('.order-support .status').html()).to.contain(
+ 'delta=top');
+ expect(this.$el.find('.order-support .status').html()).to.contain(
+ 'id=item9');
+ // No items actually moved, this is to be implemented server-side.
+ });
+
+ it('test itemRow actionmenu selectAll click', function() {
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+
+ var folder = this.$el.find('.itemRow').eq(0);
+ $('.actionmenu ul li.selectAll a', folder).trigger('click');
+ this.clock.tick(1000);
+ expect($('table tbody .selection input:checked', this.$el).length
+ ).to.equal(0);
+ // all items in the folder be populated within the selection well.
+ expect(this.$el.find('#btn-selected-items').html()).to.contain('101');
+ });
+
+ it('test navigate to item', function() {
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+ var pattern = this.$el.data('patternStructure');
+ var item = this.$el.find('.itemRow').eq(10);
+ expect(item.data().id).to.equal('item9');
+ $('.title a.manage', item).trigger('click');
+ this.clock.tick(1000);
+ expect(dummyWindow.location).to.equal('http://localhost:8081/item9/view');
+
+ $('.actionmenu ul li.editItem a', item).trigger('click');
+ this.clock.tick(1000);
+ expect(dummyWindow.location).to.equal('http://localhost:8081/item9/edit');
+ });
+
+ it('test navigate to folder push states', function() {
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+ var pattern = this.$el.data('patternStructure');
+ var item = this.$el.find('.itemRow').eq(0);
+ expect(item.data().id).to.equal('folder');
+ $('.title a.manage', item).trigger('click');
+ this.clock.tick(1000);
+ expect(history.pushed.url).to.equal(
+ 'http://localhost:8081/folder/folder_contents');
+ expect(structureUrlChangedPath).to.eql('/folder');
+
+ $('.fc-breadcrumbs a', this.$el).eq(0).trigger('click');
+ this.clock.tick(1000);
+ expect(history.pushed.url).to.equal(
+ 'http://localhost:8081/folder_contents');
+ expect(structureUrlChangedPath).to.eql('');
+ });
+
+ it('test navigate to folder pop states', function() {
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+ // Need to inject this to the mocked window location attribute the
+ // code will check against. This url is set before the trigger.
+ dummyWindow.location = {
+ 'href': 'http://localhost:8081/folder/folder/folder_contents'};
+ // then trigger off the real window.
+ $(window).trigger('popstate');
+ this.clock.tick(1000);
+ expect(structureUrlChangedPath).to.eql('/folder/folder');
+ });
+
});
+
+ /* ==========================
+ TEST: Structure Customized
+ ========================== */
+ describe('Structure Customized', function() {
+ beforeEach(function() {
+ // clear cookie setting
+ $.removeCookie('_fc_perPage');
+
+ var structure = {
+ "vocabularyUrl": "/data.json",
+ "indexOptionsUrl": "/tests/json/queryStringCriteria.json",
+ "contextInfoUrl": "{path}/contextInfo",
+ "activeColumnsCookie": "activeColumnsCustom",
+ "buttons": [{
+ "url": "foo",
+ "title": "Foo",
+ "id": "foo",
+ "icon": ""
+ }]
+ };
+
+ this.$el = $('').attr(
+ 'data-pat-structure', JSON.stringify(structure)).appendTo('body');
+
+ this.server = sinon.fakeServer.create();
+ this.server.autoRespond = true;
+
+ this.server.respondWith('GET', /data.json/, function (xhr, id) {
+ var batch = JSON.parse(getQueryVariable(xhr.url, 'batch'));
+ var start = 0;
+ var end = 15;
+ if (batch) {
+ start = (batch.page - 1) * batch.size;
+ end = start + batch.size;
+ }
+ var items = [];
+
+ xhr.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({
+ total: 0,
+ results: items
+ }));
+ });
+ this.server.respondWith('GET', /contextInfo/, function (xhr, id) {
+ var data = {
+ addButtons: []
+ };
+ if (xhr.url.indexOf('folder') !== -1){
+ data.object = {
+ UID: '123sdfasdfFolder',
+ getURL: 'http://localhost:8081/folder',
+ path: '/folder',
+ portal_type: 'Folder',
+ Description: 'folder',
+ Title: 'Folder',
+ 'review_state': 'published',
+ 'is_folderish': true,
+ Subject: [],
+ id: 'folder'
+ };
+ }
+ xhr.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify(data));
+ });
+
+ this.clock = sinon.useFakeTimers();
+
+ sinon.stub(utils, 'getWindow', function() {
+ return dummyWindow;
+ });
+
+ this.sandbox = sinon.sandbox.create();
+ this.sandbox.stub(window, 'history', history);
+ });
+
+ afterEach(function() {
+ this.server.restore();
+ this.clock.restore();
+ this.sandbox.restore();
+ $('body').html('');
+ utils.getWindow.restore();
+ $('body').off('structure-url-changed');
+ });
+
+ it('initialize', function() {
+ registry.scan(this.$el);
+ // no order-support for this one due to lack of moveUrl
+ expect(this.$el.find('.order-support > table').size()).to.equal(0);
+ });
+
+ it('per page', function() {
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+ this.$el.find('.serverhowmany15 a').trigger('click');
+ this.clock.tick(1000);
+ expect(this.$el.find('.itemRow').length).to.equal(0);
+ this.$el.find('.serverhowmany30 a').trigger('click');
+ this.clock.tick(1000);
+ expect(this.$el.find('.itemRow').length).to.equal(0);
+ });
+
+ it('test select all', function() {
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+ var $item = this.$el.find('table th .select-all');
+ $item[0].checked = true;
+ $item.trigger('change');
+ this.clock.tick(1000);
+ expect(this.$el.find('#btn-selected-items').html()).to.contain('0');
+
+ });
+
+ it('test unselect all', function() {
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+ var $item = this.$el.find('table th .select-all');
+ $item[0].checked = true;
+ $item.trigger('change');
+ this.clock.tick(1000);
+ expect(this.$el.find('#btn-selected-items').html()).to.contain('0');
+ $item[0].checked = false;
+ $item.trigger('change');
+ this.clock.tick(1000);
+ expect(this.$el.find('#btn-selected-items').html()).to.contain('0');
+ });
+
+ it('test select displayed columns', function() {
+ registry.scan(this.$el);
+ // manually setting a borrowed cookie from the previous test.
+ $.cookie('_fc_activeColumns',
+ '{"value":["ModificationDate","EffectiveDate","review_state",' +
+ '"getObjSize"]}');
+ this.clock.tick(500);
+ var $row = this.$el.find('table thead tr').eq(1);
+ expect($row.find('th').length).to.equal(6);
+ expect($row.find('th').eq(5).text()).to.equal('Actions');
+
+ expect($.cookie('_fc_activeColumnsCustom')).to.be(undefined);
+
+ this.$el.find('#btn-attribute-columns').trigger('click');
+ this.clock.tick(500);
+
+ var $checkbox = this.$el.find(
+ '.attribute-columns input[value="portal_type"]');
+ $checkbox[0].checked = true;
+ $checkbox.trigger('change');
+ this.clock.tick(500);
+
+ var $popover = this.$el.find('.popover.attribute-columns');
+ expect($popover.find('button').text()).to.equal('Save');
+ $popover.find('button').trigger('click');
+ this.clock.tick(500);
+
+ $row = this.$el.find('table thead tr').eq(1);
+ expect($row.find('th').length).to.equal(7);
+ expect($row.find('th').eq(5).text()).to.equal('Type');
+ expect($row.find('th').eq(6).text()).to.equal('Actions');
+ expect($.parseJSON($.cookie('_fc_activeColumnsCustom')).value).to.eql(
+ ["ModificationDate", "EffectiveDate", "review_state", "portal_type"]);
+ // standard cookie unchanged.
+ expect($.parseJSON($.cookie('_fc_activeColumns')).value).to.eql(
+ ["ModificationDate", "EffectiveDate", "review_state", "getObjSize"]);
+
+ $checkbox[0].checked = false;
+ $checkbox.trigger('change');
+ $popover.find('button').trigger('click');
+ this.clock.tick(500);
+
+ $row = this.$el.find('table thead tr').eq(1);
+ expect($row.find('th').length).to.equal(6);
+ expect($.parseJSON($.cookie('_fc_activeColumnsCustom')).value).to.eql(
+ ["ModificationDate", "EffectiveDate", "review_state"]);
+
+ });
+
+ it('test main buttons count', function() {
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+ var buttons = this.$el.find('#btngroup-mainbuttons a');
+ expect(buttons.length).to.equal(1);
+ });
+
+ });
+
+
+ /* ==========================
+ TEST: Structure no buttons
+ ========================== */
+ describe('Structure no buttons', function() {
+ beforeEach(function() {
+ // clear cookie setting
+ $.removeCookie('_fc_perPage');
+ $.removeCookie('_fc_activeColumnsCustom');
+
+ var structure = {
+ "vocabularyUrl": "/data.json",
+ "indexOptionsUrl": "/tests/json/queryStringCriteria.json",
+ "contextInfoUrl": "{path}/contextInfo",
+ "activeColumnsCookie": "activeColumnsCustom",
+ "activeColumns": ["getObjSize"],
+ "availableColumns": {
+ "id": "ID",
+ "CreationDate": "Created",
+ "getObjSize": "Object Size"
+ },
+ "buttons": []
+ };
+
+ this.$el = $('').attr(
+ 'data-pat-structure', JSON.stringify(structure)).appendTo('body');
+
+ this.server = sinon.fakeServer.create();
+ this.server.autoRespond = true;
+
+ this.server.respondWith('GET', /data.json/, function (xhr, id) {
+ var batch = JSON.parse(getQueryVariable(xhr.url, 'batch'));
+ var start = 0;
+ var end = 15;
+ if (batch) {
+ start = (batch.page - 1) * batch.size;
+ end = start + batch.size;
+ }
+ var items = [];
+
+ xhr.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({
+ total: 0,
+ results: items
+ }));
+ });
+ this.server.respondWith('GET', /contextInfo/, function (xhr, id) {
+ var data = {
+ addButtons: []
+ };
+ if (xhr.url.indexOf('folder') !== -1){
+ data.object = {
+ UID: '123sdfasdfFolder',
+ getURL: 'http://localhost:8081/folder',
+ path: '/folder',
+ portal_type: 'Folder',
+ Description: 'folder',
+ Title: 'Folder',
+ 'review_state': 'published',
+ 'is_folderish': true,
+ Subject: [],
+ id: 'folder'
+ };
+ }
+ xhr.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify(data));
+ });
+
+ this.clock = sinon.useFakeTimers();
+ });
+
+ afterEach(function() {
+ this.server.restore();
+ this.clock.restore();
+ $('body').html('');
+ });
+
+ it('test main buttons count', function() {
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+ var buttons = this.$el.find('#btngroup-mainbuttons a');
+ expect(buttons.length).to.equal(0);
+ });
+
+ it('test select displayed columns', function() {
+ registry.scan(this.$el);
+ this.clock.tick(500);
+ var $row = this.$el.find('table thead tr').eq(1);
+ expect($row.find('th').length).to.equal(4);
+ expect($row.find('th').eq(1).text()).to.equal('Title');
+ expect($row.find('th').eq(2).text()).to.equal('Object Size');
+ expect($row.find('th').eq(3).text()).to.equal('Actions');
+ });
+
+ });
+
+ /* ==========================
+ TEST: Structure barebone columns
+ ========================== */
+ describe('Structure barebone columns', function() {
+ beforeEach(function() {
+ // clear cookie setting
+ $.removeCookie('_fc_perPage');
+ $.removeCookie('_fc_activeColumnsCustom');
+
+ var structure = {
+ "vocabularyUrl": "/data.json",
+ "indexOptionsUrl": "/tests/json/queryStringCriteria.json",
+ "contextInfoUrl": "{path}/contextInfo",
+ "activeColumnsCookie": "activeColumnsCustom",
+ "activeColumns": [],
+ "availableColumns": {
+ "getURL": "URL",
+ },
+ "buttons": [],
+ "attributes": [
+ 'Title', 'getURL'
+ ]
+ };
+
+ this.$el = $('').attr(
+ 'data-pat-structure', JSON.stringify(structure)).appendTo('body');
+
+ this.server = sinon.fakeServer.create();
+ this.server.autoRespond = true;
+
+ this.server.respondWith('GET', /data.json/, function (xhr, id) {
+ var batch = JSON.parse(getQueryVariable(xhr.url, 'batch'));
+ var start = 0;
+ var end = 15;
+ if (batch) {
+ start = (batch.page - 1) * batch.size;
+ end = start + batch.size;
+ }
+ var items = [];
+ items.push({
+ /*
+ getURL: 'http://localhost:8081/folder',
+ Title: 'Folder',
+ id: 'folder'
+ */
+ // 'portal_type', 'review_state', 'getURL'
+
+ getURL: 'http://localhost:8081/folder',
+ Title: 'Folder',
+ // Other required fields.
+ id: 'folder',
+ UID: 'folder'
+ });
+ for (var i = start; i < end; i = i + 1) {
+ items.push({
+ /*
+ getURL: 'http://localhost:8081/item' + i,
+ Title: 'Document ' + i,
+ */
+
+ getURL: 'http://localhost:8081/item' + i,
+ Title: 'Document ' + i,
+ // Other required fields.
+ id: 'item' + i,
+ UID: 'item' + i
+ });
+ }
+
+ xhr.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({
+ total: 100,
+ results: items
+ }));
+ });
+ this.server.respondWith('GET', /contextInfo/, function (xhr, id) {
+ var data = {
+ addButtons: []
+ };
+ if (xhr.url.indexOf('folder') !== -1){
+ data.object = {
+ UID: '123sdfasdfFolder',
+ getURL: 'http://localhost:8081/folder',
+ path: '/folder',
+ portal_type: 'Folder',
+ Description: 'folder',
+ Title: 'Folder',
+ 'review_state': 'published',
+ 'is_folderish': true,
+ Subject: [],
+ id: 'folder'
+ };
+ }
+ xhr.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify(data));
+ });
+
+ this.clock = sinon.useFakeTimers();
+ });
+
+ afterEach(function() {
+ this.server.restore();
+ this.clock.restore();
+ $('body').html('');
+ });
+
+ it('per page', function() {
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+ this.$el.find('.serverhowmany15 a').trigger('click');
+ this.clock.tick(1000);
+ expect(this.$el.find('.itemRow').length).to.equal(16);
+ this.$el.find('.serverhowmany30 a').trigger('click');
+ this.clock.tick(1000);
+ expect(this.$el.find('.itemRow').length).to.equal(31);
+ });
+
+ it('test itemRow actionmenu move-top none', function() {
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+ // top item
+ var item = this.$el.find('.itemRow').eq(1);
+ expect(item.data().id).to.equal('item0');
+ // Since no moveUrl, no move-top or move-bottom.
+ expect(item.find('.actionmenu .move-top a').length).to.equal(0);
+ expect(item.find('.actionmenu .move-bottom a').length).to.equal(0);
+ });
+
+ });
+
+ /* ==========================
+ TEST: Structure no action buttons
+ ========================== */
+ describe('Structure no action buttons', function() {
+ beforeEach(function() {
+ // clear cookie setting
+ $.removeCookie('_fc_perPage');
+ $.removeCookie('_fc_activeColumnsCustom');
+
+ var structure = {
+ "vocabularyUrl": "/data.json",
+ "indexOptionsUrl": "/tests/json/queryStringCriteria.json",
+ "contextInfoUrl": "{path}/contextInfo",
+ "activeColumnsCookie": "activeColumnsCustom",
+ "activeColumns": [],
+ "availableColumns": {
+ "getURL": "URL",
+ },
+ "buttons": [],
+ "menuOptions": [],
+ "attributes": [
+ 'Title', 'getURL'
+ ]
+ };
+
+ this.$el = $('').attr(
+ 'data-pat-structure', JSON.stringify(structure)).appendTo('body');
+
+ this.server = sinon.fakeServer.create();
+ this.server.autoRespond = true;
+
+ this.server.respondWith('GET', /data.json/, function (xhr, id) {
+ var batch = JSON.parse(getQueryVariable(xhr.url, 'batch'));
+ var start = 0;
+ var end = 15;
+ if (batch) {
+ start = (batch.page - 1) * batch.size;
+ end = start + batch.size;
+ }
+ var items = [];
+ items.push({
+ getURL: 'http://localhost:8081/folder',
+ Title: 'Folder',
+ id: 'folder',
+ UID: 'folder',
+ });
+ for (var i = start; i < end; i = i + 1) {
+ items.push({
+ getURL: 'http://localhost:8081/item' + i,
+ Title: 'Document ' + i,
+ id: 'item' + 1,
+ UID: 'item' + 1,
+ });
+ }
+
+ xhr.respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
+ total: 100,
+ results: items
+ }));
+ });
+ this.server.respondWith('GET', /contextInfo/, function (xhr, id) {
+ var data = {
+ addButtons: []
+ };
+ if (xhr.url.indexOf('folder') !== -1){
+ data.object = {
+ UID: '123sdfasdfFolder',
+ getURL: 'http://localhost:8081/folder',
+ path: '/folder',
+ portal_type: 'Folder',
+ Description: 'folder',
+ Title: 'Folder',
+ 'review_state': 'published',
+ 'is_folderish': true,
+ Subject: [],
+ id: 'folder'
+ };
+ }
+ xhr.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify(data));
+ });
+
+ this.clock = sinon.useFakeTimers();
+ });
+
+ afterEach(function() {
+ this.server.restore();
+ this.clock.restore();
+ $('body').html('');
+ });
+
+ it('test itemRow actionmenu no options.', function() {
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+ // top item
+ var item = this.$el.find('.itemRow').eq(1);
+ expect(item.data().id).to.equal('item1');
+ // Since no moveUrl, no move-top or move-bottom.
+ expect(item.find('.actionmenu * a').length).to.equal(0);
+ });
+
+ });
+
+ /* ==========================
+ TEST: Structure alternative action buttons
+ ========================== */
+ describe('Structure alternative action buttons and links', function() {
+ beforeEach(function() {
+ // clear cookie setting
+ $.removeCookie('_fc_perPage');
+ $.removeCookie('_fc_activeColumnsCustom');
+
+ var structure = {
+ "vocabularyUrl": "/data.json",
+ "indexOptionsUrl": "/tests/json/queryStringCriteria.json",
+ "contextInfoUrl": "{path}/contextInfo",
+ "activeColumnsCookie": "activeColumnsCustom",
+ "activeColumns": [],
+ "availableColumns": {
+ "getURL": "URL",
+ },
+ "buttons": [],
+ "menuOptions": {
+ 'action1': [
+ 'dummytestaction',
+ 'option1',
+ '#',
+ 'Option 1',
+ ],
+ 'action2': [
+ 'dummytestaction',
+ 'option2',
+ '#',
+ 'Option 2',
+ ],
+ },
+ 'tableRowItemAction': {
+ 'other': ['dummytestaction', 'handleOther'],
+ },
+ "attributes": [
+ 'Title', 'getURL'
+ ],
+ };
+
+ this.$el = $('').attr(
+ 'data-pat-structure', JSON.stringify(structure)).appendTo('body');
+
+ $('body').off('structure-url-changed').on('structure-url-changed',
+ function (e, path) {
+ structureUrlChangedPath = path;
+ }
+ );
+
+ this.server = sinon.fakeServer.create();
+ this.server.autoRespond = true;
+
+ this.server.respondWith('GET', /data.json/, function (xhr, id) {
+ var batch = JSON.parse(getQueryVariable(xhr.url, 'batch'));
+ var start = 0;
+ var end = 15;
+ if (batch) {
+ start = (batch.page - 1) * batch.size;
+ end = start + batch.size;
+ }
+ var items = [{
+ getURL: 'http://localhost:8081/folder',
+ Title: 'Folder',
+ 'is_folderish': true,
+ path: '/folder',
+ id: 'folder'
+ }, {
+ getURL: 'http://localhost:8081/item',
+ Title: 'Item',
+ 'is_folderish': false,
+ path: '/item',
+ id: 'item'
+ }];
+
+ xhr.respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
+ total: 1,
+ results: items
+ }));
+ });
+ this.server.respondWith('GET', /contextInfo/, function (xhr, id) {
+ var data = {
+ addButtons: []
+ };
+ if (xhr.url.indexOf('folder') !== -1){
+ data.object = {
+ UID: '123sdfasdfFolder',
+ getURL: 'http://localhost:8081/folder',
+ path: '/folder',
+ portal_type: 'Folder',
+ Description: 'folder',
+ Title: 'Folder',
+ 'review_state': 'published',
+ 'is_folderish': true,
+ Subject: [],
+ id: 'folder'
+ };
+ }
+ xhr.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify(data));
+ });
+
+ this.clock = sinon.useFakeTimers();
+ this.sandbox = sinon.sandbox.create();
+ this.sandbox.stub(window, 'history', history);
+
+ sinon.stub(utils, 'getWindow', function() {
+ return dummyWindow;
+ });
+ });
+
+ afterEach(function() {
+ requirejs.undef('dummytestaction');
+ utils.getWindow.restore();
+ this.sandbox.restore();
+ this.server.restore();
+ this.clock.restore();
+ $('body').html('');
+ $('body').off('structure-url-changed');
+ });
+
+ it('test itemRow actionmenu custom options.', function() {
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+ var item = this.$el.find('.itemRow').eq(0);
+ // Check for complete new options
+ expect($('.actionmenu * a', item).length).to.equal(2);
+ expect($('.actionmenu .action1 a', item).text()).to.equal('Option 1');
+ expect($('.actionmenu .action2 a', item).text()).to.equal('Option 2');
+
+ define('dummytestaction', ['backbone'], function(Backbone) {
+ var Actions = Backbone.Model.extend({
+ initialize: function(options) {
+ this.options = options;
+ this.app = options.app;
+ },
+ option1: function(e) {
+ e.preventDefault();
+ var self = this;
+ self.app.setStatus('Status: option1 selected');
+ },
+ option2: function(e) {
+ e.preventDefault();
+ var self = this;
+ self.app.setStatus('Status: option2 selected');
+ }
+ });
+ return Actions;
+ });
+ // preload the defined module to allow it be used synchronously.
+ require(['dummytestaction'], function(){});
+ this.clock.tick(1000);
+
+ $('.actionmenu .action1 a', item).trigger('click');
+ this.clock.tick(1000);
+ // status will be set as defined.
+ expect($('.status').text()).to.contain('Status: option1 selected');
+
+ $('.actionmenu .action2 a', item).trigger('click');
+ this.clock.tick(1000);
+ // status will be set as defined.
+ expect($('.status').text()).to.contain('Status: option2 selected');
+ });
+
+ it('folder link not overriden', function() {
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+ var item = this.$el.find('.itemRow').eq(0);
+ $('.title a.manage', item).trigger('click');
+ this.clock.tick(1000);
+ // default action will eventually trigger this.
+ expect(this.$el.find('.context-buttons').length).to.equal(1);
+ });
+
+ it('item link triggered', function() {
+ define('dummytestaction', ['backbone'], function(Backbone) {
+ var Actions = Backbone.Model.extend({
+ initialize: function(options) {
+ this.options = options;
+ this.app = options.app;
+ },
+ handleOther: function(e) {
+ e.preventDefault();
+ var self = this;
+ self.app.setStatus('Status: not a folder');
+ }
+ });
+ return Actions;
+ });
+ // preload the defined module to allow it be used synchronously.
+ require(['dummytestaction'], function(){});
+
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+ var item = this.$el.find('.itemRow').eq(1);
+ $('.title a.manage', item).trigger('click');
+ this.clock.tick(1000);
+ // status will be set as defined.
+ expect($('.status').text()).to.contain('Status: not a folder');
+ });
+
+ });
+
+ /* ==========================
+ TEST: Structure traverse subpath
+ ========================== */
+ describe('Structure traverse links', function() {
+ beforeEach(function() {
+ // clear cookie setting
+ $.removeCookie('_fc_perPage');
+ $.removeCookie('_fc_activeColumnsCustom');
+
+ this.structure = {
+ "vocabularyUrl": "/data.json",
+ "indexOptionsUrl": "/tests/json/queryStringCriteria.json",
+ "contextInfoUrl": "{path}/contextInfo",
+ "activeColumnsCookie": "activeColumnsCustom",
+ "activeColumns": [],
+ "availableColumns": {
+ "getURL": "URL",
+ },
+ "buttons": [],
+ "attributes": [
+ 'Title', 'getURL'
+ ],
+ "urlStructure": {
+ "base": "http://localhost:8081/traverse_view",
+ "appended": ""
+ },
+ "pushStateUrl": "http://localhost:8081/traverse_view{path}",
+ "traverseView": true
+ };
+
+ $('body').off('structure-url-changed').on('structure-url-changed',
+ function (e, path) {
+ structureUrlChangedPath = path;
+ }
+ );
+
+ this.server = sinon.fakeServer.create();
+ this.server.autoRespond = true;
+
+ this.server.respondWith('GET', /data.json/, function (xhr, id) {
+ var batch = JSON.parse(getQueryVariable(xhr.url, 'batch'));
+ var start = 0;
+ var end = 15;
+ if (batch) {
+ start = (batch.page - 1) * batch.size;
+ end = start + batch.size;
+ }
+ var items = [{
+ getURL: 'http://localhost:8081/folder',
+ Title: 'Folder',
+ 'is_folderish': true,
+ path: '/folder',
+ id: 'folder'
+ }, {
+ getURL: 'http://localhost:8081/item',
+ Title: 'Item',
+ 'is_folderish': false,
+ path: '/item',
+ id: 'item'
+ }];
+
+ xhr.respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
+ total: 1,
+ results: items
+ }));
+ });
+ this.server.respondWith('GET', /contextInfo/, function (xhr, id) {
+ var data = {
+ addButtons: []
+ };
+ if (xhr.url.indexOf('folder') !== -1){
+ data.object = {
+ UID: '123sdfasdfFolder',
+ getURL: 'http://localhost:8081/folder',
+ path: '/folder',
+ portal_type: 'Folder',
+ Description: 'folder',
+ Title: 'Folder',
+ 'review_state': 'published',
+ 'is_folderish': true,
+ Subject: [],
+ id: 'folder'
+ };
+ }
+ xhr.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify(data));
+ });
+
+ this.clock = sinon.useFakeTimers();
+ this.sandbox = sinon.sandbox.create();
+ this.sandbox.stub(window, 'history', history);
+
+ sinon.stub(utils, 'getWindow', function() {
+ return dummyWindow;
+ });
+ });
+
+ afterEach(function() {
+ requirejs.undef('dummytestaction');
+ utils.getWindow.restore();
+ this.sandbox.restore();
+ this.server.restore();
+ this.clock.restore();
+ $('body').html('');
+ $('body').off('structure-url-changed');
+ });
+
+ it('test navigate to folder push states - urlStructure', function() {
+ delete this.structure.pushStateUrl;
+ this.$el = $('').attr(
+ 'data-pat-structure', JSON.stringify(this.structure)).appendTo('body');
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+ var pattern = this.$el.data('patternStructure');
+ var item = this.$el.find('.itemRow').eq(0);
+ expect(item.data().id).to.equal('folder');
+ $('.title a.manage', item).trigger('click');
+ this.clock.tick(1000);
+ expect(history.pushed.url).to.equal(
+ 'http://localhost:8081/traverse_view/folder');
+ expect(structureUrlChangedPath).to.eql('');
+
+ $('.fc-breadcrumbs a', this.$el).eq(0).trigger('click');
+ this.clock.tick(1000);
+ expect(history.pushed.url).to.equal(
+ 'http://localhost:8081/traverse_view');
+ });
+
+ it('test navigate to folder pop states - urlStructure', function() {
+ delete this.structure.pushStateUrl;
+ this.$el = $('').attr(
+ 'data-pat-structure', JSON.stringify(this.structure)).appendTo('body');
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+ // Need to inject this to the mocked window location attribute the
+ // code will check against. This url is set before the trigger.
+ dummyWindow.location = {
+ 'href': 'http://localhost:8081/traverse_view/folder/folder'};
+ // then trigger off the real window.
+ $(window).trigger('popstate');
+ this.clock.tick(1000);
+ expect(structureUrlChangedPath).to.eql('/folder/folder');
+ });
+
+ it('test navigate to folder push states - pushStateUrl', function() {
+ delete this.structure.urlStructure;
+ this.$el = $('').attr(
+ 'data-pat-structure', JSON.stringify(this.structure)).appendTo('body');
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+ var pattern = this.$el.data('patternStructure');
+ var item = this.$el.find('.itemRow').eq(0);
+ expect(item.data().id).to.equal('folder');
+ $('.title a.manage', item).trigger('click');
+ this.clock.tick(1000);
+ expect(history.pushed.url).to.equal(
+ 'http://localhost:8081/traverse_view/folder');
+ expect(structureUrlChangedPath).to.eql('');
+
+ $('.fc-breadcrumbs a', this.$el).eq(0).trigger('click');
+ this.clock.tick(1000);
+ expect(history.pushed.url).to.equal(
+ 'http://localhost:8081/traverse_view');
+ });
+
+ it('test navigate to folder pop states - pushStateUrl', function() {
+ delete this.structure.urlStructure;
+ this.$el = $('').attr(
+ 'data-pat-structure', JSON.stringify(this.structure)).appendTo('body');
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+ // Need to inject this to the mocked window location attribute the
+ // code will check against. This url is set before the trigger.
+ dummyWindow.location = {
+ 'href': 'http://localhost:8081/traverse_view/folder/folder'};
+ // then trigger off the real window.
+ $(window).trigger('popstate');
+ this.clock.tick(1000);
+ expect(structureUrlChangedPath).to.eql('/folder/folder');
+ });
+
+ });
+
+ /* ==========================
+ TEST: Structure action menu generator
+ ========================== */
+ describe('Structure alternative action buttons and links', function() {
+ beforeEach(function() {
+ // clear cookie setting
+ $.removeCookie('_fc_perPage');
+ $.removeCookie('_fc_activeColumnsCustom');
+
+ var structure = {
+ "vocabularyUrl": "/data.json",
+ "indexOptionsUrl": "/tests/json/queryStringCriteria.json",
+ "contextInfoUrl": "{path}/contextInfo",
+ "activeColumnsCookie": "activeColumnsCustom",
+ "activeColumns": [],
+ "availableColumns": {
+ "getURL": "URL",
+ },
+ "buttons": [],
+ "menuGenerator": 'dummyactionmenu',
+ 'tableRowItemAction': {
+ 'other': ['dummytestaction', 'handleOther'],
+ },
+ "attributes": [
+ 'Title', 'getURL'
+ ],
+ "traverseView": true
+ };
+
+ this.$el = $('').attr(
+ 'data-pat-structure', JSON.stringify(structure)).appendTo('body');
+
+ $('body').off('structure-url-changed').on('structure-url-changed',
+ function (e, path) {
+ structureUrlChangedPath = path;
+ }
+ );
+
+ this.server = sinon.fakeServer.create();
+ this.server.autoRespond = true;
+
+ this.server.respondWith('GET', /data.json/, function (xhr, id) {
+ var batch = JSON.parse(getQueryVariable(xhr.url, 'batch'));
+ var start = 0;
+ var end = 15;
+ if (batch) {
+ start = (batch.page - 1) * batch.size;
+ end = start + batch.size;
+ }
+ var items = [{
+ getURL: 'http://localhost:8081/folder',
+ Title: 'Folder',
+ 'is_folderish': true,
+ path: '/folder',
+ id: 'folder'
+ }, {
+ getURL: 'http://localhost:8081/item',
+ Title: 'Item',
+ 'is_folderish': false,
+ path: '/item',
+ id: 'item'
+ }];
+
+ xhr.respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
+ total: 2,
+ results: items
+ }));
+ });
+ this.server.respondWith('GET', /contextInfo/, function (xhr, id) {
+ var data = {
+ addButtons: []
+ };
+ if (xhr.url.indexOf('folder') !== -1){
+ data.object = {
+ UID: '123sdfasdfFolder',
+ getURL: 'http://localhost:8081/folder',
+ path: '/folder',
+ portal_type: 'Folder',
+ Description: 'folder',
+ Title: 'Folder',
+ 'review_state': 'published',
+ 'is_folderish': true,
+ Subject: [],
+ id: 'folder'
+ };
+ }
+ xhr.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify(data));
+ });
+
+ this.clock = sinon.useFakeTimers();
+ this.sandbox = sinon.sandbox.create();
+ this.sandbox.stub(window, 'history', history);
+ });
+
+ afterEach(function() {
+ requirejs.undef('dummytestaction');
+ requirejs.undef('dummyactionmenu');
+ this.sandbox.restore();
+ this.server.restore();
+ this.clock.restore();
+ $('body').html('');
+ $('body').off('structure-url-changed');
+ });
+
+ it('test itemRow actionmenu generated options from model.', function() {
+ define('dummytestaction', ['backbone'], function(Backbone) {
+ var Actions = Backbone.Model.extend({
+ initialize: function(options) {
+ this.options = options;
+ this.app = options.app;
+ },
+ folderClicker: function(e) {
+ e.preventDefault();
+ var self = this;
+ self.app.setStatus('Status: folder clicked');
+ },
+ itemClicker: function(e) {
+ e.preventDefault();
+ var self = this;
+ self.app.setStatus('Status: item clicked');
+ }
+ });
+ return Actions;
+ });
+
+ define('dummyactionmenu', [], function() {
+ var ActionMenu = function(menu) {
+ if (menu.model.attributes.id === 'item') {
+ return {
+ 'itemClicker': [
+ 'dummytestaction',
+ 'itemClicker',
+ '#',
+ 'Item Clicker'
+ ]
+ };
+ } else {
+ return {
+ 'folderClicker': [
+ 'dummytestaction',
+ 'folderClicker',
+ '#',
+ 'Folder Clicker'
+ ]
+ };
+ }
+ };
+ return ActionMenu;
+ });
+
+ // preload the defined module to allow it be used synchronously.
+ require(['dummytestaction'], function(){});
+ require(['dummyactionmenu'], function(){});
+
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+
+ var folder = this.$el.find('.itemRow').eq(0);
+
+ // Check for complete new options
+ expect($('.actionmenu * a', folder).length).to.equal(1);
+ expect($('.actionmenu .folderClicker a', folder).text()).to.equal(
+ 'Folder Clicker');
+ $('.actionmenu .folderClicker a', folder).trigger('click');
+ this.clock.tick(1000);
+ // status will be set as defined.
+ expect($('.status').text()).to.contain('Status: folder clicked');
+
+ var item = this.$el.find('.itemRow').eq(1);
+ // Check for complete new options
+ expect($('.actionmenu * a', item).length).to.equal(1);
+ expect($('.actionmenu .itemClicker a', item).text()).to.equal(
+ 'Item Clicker');
+ $('.actionmenu .itemClicker a', item).trigger('click');
+ this.clock.tick(1000);
+ // status will be set as defined.
+ expect($('.status').text()).to.contain('Status: item clicked');
+
+ });
+
+ it('test itemRow actionmenu malform generation.', function() {
+ // Potential failure case where user defined ActionMenu function
+ // fails to return a result, causing undefined behavior.
+ define('dummytestaction', ['backbone'], function(Backbone) {
+ // Not testing clicking here so barebone definition here.
+ var Actions = Backbone.Model.extend({
+ initialize: function(options) {
+ this.options = options;
+ this.app = options.app;
+ }
+ });
+ return Actions;
+ });
+
+ define('dummyactionmenu', [], function() {
+ var ActionMenu = function(menu) {
+ // return undefined
+ };
+ return ActionMenu;
+ });
+
+ // preload the defined module to allow it be used synchronously.
+ require(['dummytestaction'], function(){});
+ require(['dummyactionmenu'], function(){});
+
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+
+ // ensure that the items have been properly generated.
+ expect(this.$el.find('.itemRow').length).to.equal(2);
+
+ });
+
+ });
+
+ /* ==========================
+ TEST: Structure custom URLs
+ ========================== */
+ describe('Structure custom data URLs', function() {
+ beforeEach(function() {
+ // clear cookie setting
+ $.removeCookie('__cp');
+ $.removeCookie('_fc_perPage');
+ $.removeCookie('_fc_activeColumns');
+ $.removeCookie('_fc_activeColumnsCustom');
+
+ this.$el = $('' +
+ '' +
+ ' ').appendTo('body');
+
+ this.server = sinon.fakeServer.create();
+ this.server.autoRespond = true;
+
+ this.server.respondWith('GET', /data.json/, function (xhr, id) {
+ var batch = JSON.parse(getQueryVariable(xhr.url, 'batch'));
+ var start = 0;
+ var end = 15;
+ if (batch) {
+ start = (batch.page - 1) * batch.size;
+ end = start + batch.size;
+ }
+ var items = [];
+ items.push({
+ UID: '123sdfasdfFolder',
+ getURL: 'http://localhost:8081/folder',
+ viewURL: 'http://localhost:8081/folder',
+ path: '/folder',
+ portal_type: 'Folder',
+ Description: 'folder',
+ Title: 'Folder',
+ 'review_state': 'published',
+ 'is_folderish': true,
+ Subject: [],
+ id: 'folder'
+ });
+ for (var i = start; i < end; i = i + 1) {
+ items.push({
+ UID: '123sdfasdf' + i,
+ getURL: 'http://localhost:8081/item' + i,
+ viewURL: 'http://localhost:8081/item' + i + '/item_view',
+ path: '/item' + i,
+ portal_type: 'Document ' + i,
+ Description: 'document',
+ Title: 'Document ' + i,
+ 'review_state': 'published',
+ 'is_folderish': false,
+ Subject: [],
+ id: 'item' + i
+ });
+ }
+
+ xhr.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({
+ total: 100,
+ results: items
+ }));
+ });
+
+ this.server.respondWith('GET', /contextInfo/, function (xhr, id) {
+ var data = {
+ addButtons: [{
+ id: 'document',
+ title: 'Document',
+ url: '/adddocument'
+ },{
+ id: 'folder',
+ title: 'Folder'
+ }],
+ };
+ if (xhr.url.indexOf('folder') !== -1){
+ data.object = {
+ UID: '123sdfasdfFolder',
+ getURL: 'http://localhost:8081/folder',
+ path: '/folder',
+ portal_type: 'Folder',
+ Description: 'folder',
+ Title: 'Folder',
+ 'review_state': 'published',
+ 'is_folderish': true,
+ Subject: [],
+ id: 'folder'
+ };
+ }
+ xhr.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify(data));
+ });
+
+ this.clock = sinon.useFakeTimers();
+
+ sinon.stub(utils, 'getWindow', function() {
+ return dummyWindow;
+ });
+
+ });
+
+ afterEach(function() {
+ this.server.restore();
+ this.clock.restore();
+ $('body').html('');
+ utils.getWindow.restore();
+ });
+
+ it('test displayed content', function() {
+ registry.scan(this.$el);
+ this.clock.tick(500);
+
+ var $content_row = this.$el.find('table tbody tr').eq(0);
+ expect($content_row.find('td').length).to.equal(6);
+ expect($content_row.find('td').eq(1).text().trim()).to.equal('Folder');
+ expect($content_row.find('td').eq(2).text().trim()).to.equal('');
+ expect($content_row.find('td').eq(3).text().trim()).to.equal('');
+ expect($content_row.find('td').eq(4).text().trim()).to.equal('published');
+ expect($content_row.find('td .icon-group-right a').attr('href')
+ ).to.equal('http://localhost:8081/folder');
+
+ var $content_row1 = this.$el.find('table tbody tr').eq(1);
+ expect($content_row1.find('td').eq(1).text().trim()).to.equal(
+ 'Document 0');
+ expect($content_row1.find('td .icon-group-right a').attr('href')
+ ).to.equal('http://localhost:8081/item0/item_view');
+ });
+
+ });
+
+ /* ==========================
+ TEST: Structure data insufficient fields
+ ========================== */
+ describe('Structure data insufficient fields', function() {
+ beforeEach(function() {
+ // clear cookie setting
+ $.removeCookie('_fc_perPage');
+ $.removeCookie('_fc_activeColumnsCustom');
+
+ var structure = {
+ "vocabularyUrl": "/data.json",
+ "uploadUrl": "/upload",
+ "moveUrl": "/moveitem",
+ "indexOptionsUrl": "/tests/json/queryStringCriteria.json",
+ "contextInfoUrl": "{path}/contextInfo",
+ "setDefaultPageUrl": "/setDefaultPage",
+ "urlStructure": {
+ "base": "http://localhost:8081",
+ "appended": "/folder_contents"
+ }
+ };
+
+ this.$el = $('').attr(
+ 'data-pat-structure', JSON.stringify(structure)).appendTo('body');
+
+ this.server = sinon.fakeServer.create();
+ this.server.autoRespond = true;
+
+ this.server.respondWith('GET', /data.json/, function (xhr, id) {
+ var batch = JSON.parse(getQueryVariable(xhr.url, 'batch'));
+ var start = 0;
+ var end = 15;
+ if (batch) {
+ start = (batch.page - 1) * batch.size;
+ end = start + batch.size;
+ }
+ var items = [{
+ getURL: 'http://localhost:8081/folder',
+ Title: 'Folder',
+ 'is_folderish': true,
+ path: '/folder',
+ UID: 'folder',
+ id: 'folder'
+ }, {
+ getURL: 'http://localhost:8081/item',
+ Title: 'Item',
+ 'is_folderish': false,
+ path: '/item',
+ // omitting id but provide UID instead for this test
+ UID: 'item'
+ }];
+
+ xhr.respond(200, {'Content-Type': 'application/json'}, JSON.stringify({
+ total: 1,
+ results: items
+ }));
+ });
+ this.server.respondWith('GET', /contextInfo/, function (xhr, id) {
+ var data = {
+ addButtons: []
+ };
+ if (xhr.url.indexOf('folder') !== -1){
+ data.object = {
+ UID: '123sdfasdfFolder',
+ getURL: 'http://localhost:8081/folder',
+ path: '/folder',
+ portal_type: 'Folder',
+ Description: 'folder',
+ Title: 'Folder',
+ 'review_state': 'published',
+ 'is_folderish': true,
+ Subject: [],
+ id: 'folder'
+ };
+ }
+ xhr.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify(data));
+ });
+
+ this.clock = sinon.useFakeTimers();
+ });
+
+ afterEach(function() {
+ this.server.restore();
+ this.clock.restore();
+ $('body').html('');
+ });
+
+ it('test unselect all', function() {
+ registry.scan(this.$el);
+ this.clock.tick(1000);
+ var $item = this.$el.find('table th .select-all');
+ $item[0].checked = true;
+ $item.trigger('change');
+ this.clock.tick(1000);
+ expect(this.$el.find('#btn-selected-items').html()).to.contain('2');
+ expect($('table tbody .selection input:checked', this.$el).length
+ ).to.equal(2);
+
+ // XXX passing this test for now with bad data - uncheck cannot
+ // remove items without an id (but with UID specified), so this
+ // item cannot be unselected.
+ $item[0].checked = false;
+ $item.trigger('change');
+ this.clock.tick(1000);
+ expect(this.$el.find('#btn-selected-items').html()).to.contain('1');
+ expect($('table tbody .selection input:checked', this.$el).length
+ ).to.equal(1);
+ });
+
+ });
+
});
diff --git a/package.json b/package.json
index 0cc83630c..bd6542fc4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "mockup",
- "version": "2.1.4",
+ "version": "2.2.0",
"description": "A collection of client side patterns for faster and easier web development",
"homepage": "http://plone.github.io/mockup",
"devDependencies": {
|