blob: d78997d43840232d9c2a637c82d26709844c1965 [file] [log] [blame]
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
define('ace_configuration', ["app", "ace/ace"], function (app, ace) {
var path = app.host + app.root + 'js/ace';
var config = require("ace/config");
config.set("packaged", true);
config.set("workerPath", path);
config.set("modePath", path);
config.set("themePath", path);
return ace;
});
define([
"app",
// Libs
"api",
"ace_configuration",
"spin",
// this should never be global available:
// https://github.com/zeroclipboard/zeroclipboard/blob/master/docs/security.md
'addons/fauxton/dependencies/ZeroClipboard',
"velocity.ui"
],
function (app, FauxtonAPI, ace, spin, ZeroClipboard) {
var Components = FauxtonAPI.addon();
// setting up the left header with the backbutton used in Views and All docs
Components.LeftHeader = FauxtonAPI.View.extend({
className: "header-left",
template: "addons/fauxton/templates/header_left",
initialize: function (options) {
this.dropdownEvents = options.dropdownEvents;
this.dropdownMenuLinks = options.dropdownMenu;
this.lookaheadTrayOptions = options.lookaheadTrayOptions || null;
this.crumbs = options.crumbs || [];
// listen for breadcrumb clicks
this.listenTo(FauxtonAPI.Events, 'breadcrumb:click', this.toggleTray);
this.listenTo(FauxtonAPI.Events, 'lookaheadTray:close', this.unselectLastBreadcrumb);
},
updateCrumbs: function (crumbs) {
// if the breadcrumbs haven't changed, don't bother re-rendering the component
if (_.isEqual(this.crumbs, crumbs)) {
return;
}
this.crumbs = crumbs;
this.breadcrumbs && this.breadcrumbs.update(crumbs);
},
unselectLastBreadcrumb: function () {
this.breadcrumbs.unselectLastBreadcrumb();
},
toggleTray: function () {
if (this.lookaheadTray !== null) {
this.lookaheadTray.toggleTray();
}
},
beforeRender: function () {
this.setUpCrumbs();
this.setUpDropDownMenu();
if (this.lookaheadTray !== null) {
this.setUpLookaheadTray();
}
},
setUpCrumbs: function () {
this.breadcrumbs = this.insertView("#header-breadcrumbs", new Components.Breadcrumbs({
crumbs: this.crumbs
}));
},
setUpDropDownMenu: function () {
if (this.dropdownMenuLinks) {
this.dropdown = this.insertView("#header-dropdown-menu", new Components.MenuDropDown({
icon: 'fonticon-cog',
links: this.dropdownMenuLinks,
events: this.dropdownEvents
}));
}
},
setUpLookaheadTray: function () {
var options = this.lookaheadTrayOptions,
dbNames = options.databaseCollection.getDatabaseNames(),
currentDBName = this.crumbs[1].name;
// remove the current database name from the list
dbNames = _.without(dbNames, currentDBName);
this.lookaheadTray = this.insertView("#header-lookahead", new Components.LookaheadTray({
data: dbNames,
toggleEventName: options.toggleEventName,
onUpdateEventName: options.onUpdateEventName,
placeholder: options.placeholder
}));
}
});
Components.Breadcrumbs = FauxtonAPI.View.extend({
className: "breadcrumb pull-left",
tagName: "ul",
template: "addons/fauxton/templates/breadcrumbs",
events: {
"click .js-lastelement": "toggleLastElement"
},
serialize: function () {
var crumbs = _.clone(this.crumbs);
// helper template function to determine when to insert a delimiter char
var nextCrumbHasLabel = function (crumb, index) {
var nextHasLabel = crumbs[index + 1].name !== "";
return index < crumbs.length && crumb.name && nextHasLabel;
};
return {
toggleDisabled: this.toggleDisabled,
crumbs: crumbs,
nextCrumbHasLabel: nextCrumbHasLabel
};
},
toggleLastElement: function (event) {
if (this.toggleDisabled) {
return;
}
this.$(event.currentTarget).toggleClass('js-enabled');
FauxtonAPI.Events.trigger('breadcrumb:click');
},
unselectLastBreadcrumb: function () {
if (this.toggleDisabled) {
return;
}
this.$('.js-enabled').removeClass('js-enabled');
},
update: function (crumbs) {
this.crumbs = crumbs;
this.render();
},
initialize: function (options) {
this.crumbs = options.crumbs;
this.toggleDisabled = options.toggleDisabled || false;
}
});
/**
* Our generic Tray component. All trays should extend this guy - it offers some convenient boilerplate code for
* hiding/showing, event publishing and so on. The important functions that can be called on the child Views are:
* - hideTray
* - showTray
* - toggleTray
*/
Components.Tray = FauxtonAPI.View.extend({
// populated dynamically
events: {},
initTray: function (opts) {
this.toggleTrayBtnSelector = (_.has(opts, 'toggleTrayBtnSelector')) ? opts.toggleTrayBtnSelector : null;
this.onShowTray = (_.has(opts, 'onShowTray')) ? opts.onShowTray : null;
// if the component extending this one passed along the selector of the element that toggles the tray,
// add the appropriate events
if (!_.isNull(this.toggleTrayBtnSelector)) {
this.events['click ' + this.toggleTrayBtnSelector] = 'toggleTray';
}
_.bind(this.toggleTray, this);
_.bind(this.trayVisible, this);
_.bind(this.hideTray, this);
_.bind(this.showTray, this);
// a unique identifier for this tray
this.trayId = 'tray-' + this.cid;
var that = this;
$('body').on('click.' + this.trayId, function (e) {
var $clickEl = $(e.target);
if (!that.trayVisible()) {
return;
}
if (!_.isNull(that.toggleTrayBtnSelector) && $clickEl.closest(that.toggleTrayBtnSelector).length) {
return;
}
if (!$clickEl.closest('.tray').length) {
that.hideTray();
}
});
FauxtonAPI.Events.on(FauxtonAPI.constants.EVENTS.TRAY_OPENED, this.onTrayOpenEvent, this);
},
cleanup: function () {
$('body').off('click.' + this.trayId);
},
// all trays publish a EVENTS.TRAY_OPENED event containing their unique ID. This listens for those events and
// closes the current tray if it's already open
onTrayOpenEvent: function (msg) {
if (!_.has(msg, 'trayId')) {
return;
}
if (msg.trayId !== this.trayId && this.trayVisible()) {
this.hideTray();
}
},
toggleTray: function (e) {
e.preventDefault();
if (this.trayVisible()) {
this.hideTray();
} else {
this.showTray();
}
},
hideTray: function () {
var $tray = this.$('.tray');
$tray.velocity('reverse', FauxtonAPI.constants.MISC.TRAY_TOGGLE_SPEED, function () {
$tray.hide();
});
if (!_.isNull(this.toggleTrayBtnSelector)) {
this.$(this.toggleTrayBtnSelector).removeClass('enabled');
}
// announce that the tray is being closed
FauxtonAPI.Events.trigger(FauxtonAPI.constants.EVENTS.TRAY_CLOSED, { trayId: this.trayId });
},
showTray: function () {
this.$('.tray').velocity('transition.slideDownIn', FauxtonAPI.constants.MISC.TRAY_TOGGLE_SPEED);
if (!_.isNull(this.toggleTrayBtnSelector)) {
this.$(this.toggleTrayBtnSelector).addClass('enabled');
}
if (!_.isNull(this.onShowTray)) {
this.onShowTray();
}
FauxtonAPI.Events.trigger(FauxtonAPI.constants.EVENTS.TRAY_OPENED, { trayId: this.trayId });
},
trayVisible: function () {
return this.$('.tray').is(':visible');
}
});
Components.Pagination = FauxtonAPI.View.extend({
tagName: "ul",
className: 'pagination',
template: "addons/fauxton/templates/pagination",
initialize: function (options) {
this.page = parseInt(options.page, 10);
this.perPage = options.perPage;
this.urlFun = options.urlFun;
},
serialize: function () {
var total = this.collection.length;
var totalPages = Math.ceil(total / this.perPage);
var visiblePagesObject = this.getVisiblePages(this.page, totalPages);
var from = visiblePagesObject.from,
to = visiblePagesObject.to;
return {
page: this.page,
perPage: this.perPage,
total: total,
totalPages: totalPages,
urlFun: this.urlFun,
from: from,
to: to
};
},
getVisiblePages: function (page, totalPages) {
var from, to;
if (totalPages < 10) {
from = 1;
to = totalPages + 1;
} else { // if totalPages is more than 10
from = page - 5;
to = page + 5;
if (from <= 1) {
from = 1;
to = 11;
}
if (to > totalPages + 1) {
from = totalPages - 9;
to = totalPages + 1;
}
}
return {
from: from,
to: to
};
}
});
// A super-simple replacement for window.confirm()
Components.ConfirmationModal = FauxtonAPI.View.extend({
template: 'addons/fauxton/templates/confirmation_modal',
events: {
'click .js-btn-success': 'onSelectOkay'
},
initialize: function (options) {
this.options = _.extend({
title: 'Please confirm',
text: '',
action: null
}, options);
},
onSelectOkay: function () {
this.hideModal();
if (_.isFunction(this.options.action)) {
this.options.action();
}
},
showModal: function () {
this.$('.modal').modal();
$('.modal-backdrop').css('z-index', FauxtonAPI.constants.MISC.MODAL_BACKDROP_Z_INDEX);
},
hideModal: function () {
this.$('.modal').modal('hide');
},
serialize: function () {
return {
title: this.options.title,
text: this.options.text
};
}
});
Components.ModalView = FauxtonAPI.View.extend({
disableLoader: true,
initialize: function (options) {
_.bindAll(this);
},
afterRender: function () {
var that = this;
this.$('.modal').on('shown', function () {
that.$('input:text:visible:first').focus();
});
},
showModal: function () {
if (this._showModal) { this._showModal();}
this.clear_error_msg();
this.$('.modal').modal();
// hack to get modal visible
$('.modal-backdrop').css('z-index', FauxtonAPI.constants.MISC.MODAL_BACKDROP_Z_INDEX);
},
hideModal: function () {
this.$('.modal').modal('hide');
},
set_error_msg: function (msg) {
var text;
if (typeof(msg) == 'string') {
text = msg;
} else {
text = JSON.parse(msg.responseText).reason;
}
this.$('#modal-error').text(text).removeClass('hide');
},
clear_error_msg: function () {
this.$('#modal-error').text(' ').addClass('hide');
},
serialize: function () {
if (this.model) {
return this.model.toJSON();
}
return {};
}
});
Components.Typeahead = FauxtonAPI.View.extend({
initialize: function (options) {
this.source = options.source;
this.onUpdateEventName = options.onUpdateEventName;
},
afterRender: function () {
var onUpdateEventName = this.onUpdateEventName;
this.$el.typeahead({
source: this.source,
updater: function (item) {
FauxtonAPI.Events.trigger(onUpdateEventName, item);
return item;
}
});
}
});
Components.DbSearchTypeahead = Components.Typeahead.extend({
initialize: function (options) {
this.dbLimit = options.dbLimit || 30;
if (options.filter) {
this.resultFilter = options.resultFilter;
}
_.bindAll(this);
},
getURL: function (query, dbLimit) {
query = encodeURIComponent(query);
return [
app.host,
"/_all_dbs?startkey=%22",
query,
"%22&endkey=%22",
query,
encodeURIComponent("\u9999"),
"%22&limit=",
dbLimit
].join('');
},
source: function (query, process) {
var url = this.getURL(query, this.dbLimit);
var resultFilter = this.resultFilter;
if (this.ajaxReq) { this.ajaxReq.abort(); }
this.ajaxReq = $.ajax({
cache: false,
url: url,
dataType: 'json',
success: function (data) {
if (resultFilter) {
data = resultFilter(data);
}
process(data);
}
});
}
});
Components.DocSearchTypeahead = Components.Typeahead.extend({
initialize: function (options) {
this.docLimit = options.docLimit || 30;
this.database = options.database;
_.bindAll(this);
},
source: function (id, process) {
var query = '?' + $.param({
startkey: JSON.stringify(id),
endkey: JSON.stringify(id + "\u9999"),
limit: this.docLimit
});
var url = FauxtonAPI.urls('allDocs', 'server', this.database.safeID(), query);
if (this.ajaxReq) { this.ajaxReq.abort(); }
this.ajaxReq = $.ajax({
cache: false,
url: url,
dataType: 'json',
success: function (data) {
var ids = _.map(data.rows, function (row) {
return row.id;
});
process(ids);
}
});
}
});
Components.FilteredView = FauxtonAPI.View.extend({
createFilteredData: function (json) {
return _.reduce(this.filters, function (elements, filter) {
return _.filter(elements, function (element) {
var match = false;
_.each(element, function (value) {
if (new RegExp(filter, 'i').test(value.toString())) {
match = true;
}
});
return match;
});
}, json, this);
}
});
//Menu Drop down component. It takes links in this format and renders the Dropdown:
// [{
// title: 'Section Title (optional)',
// links: [{
// icon: 'icon-class (optional)',
// url: 'clickalble-url',
// title: 'name of link'
// }]
// }]
Components.MenuDropDown = FauxtonAPI.View.extend({
template: "addons/fauxton/templates/menu_dropdown",
className: "dropdown",
initialize: function (options) {
this.links = options.links;
this.icon = options.icon || "fonticon-plus-circled";
this.setUpEvents();
},
setUpEvents: function () {
this.events = {};
_.each(this.links, function (parentLink) {
_.each(parentLink.links, function (link) {
if (!link.trigger) { return; }
this.events['click .' + link.icon] = "triggerEvent";
}, this);
}, this);
},
triggerEvent: function (e) {
e.preventDefault();
var eventTrigger = $(e.currentTarget).attr('triggerEvent');
FauxtonAPI.Events.trigger(eventTrigger);
},
update: function (links) {
this.links = links;
this.render();
},
serialize: function () {
return {
links: this.links,
icon: this.icon
};
}
});
Components.LookaheadTray = FauxtonAPI.View.extend({
className: "lookahead-tray tray",
template: "addons/fauxton/templates/lookahead_tray",
placeholder: "Enter to search",
events: {
'click #js-close-tray': 'closeTray',
'keyup': 'onKeyup'
},
serialize: function () {
return {
placeholder: this.placeholder
};
},
initialize: function (opts) {
this.data = opts.data;
this.toggleEventName = opts.toggleEventName;
this.onUpdateEventName = opts.onUpdateEventName;
var trayIsVisible = _.bind(this.trayIsVisible, this);
var closeTray = _.bind(this.closeTray, this);
$("body").on("click.lookaheadTray", function (e) {
if (!trayIsVisible()) { return; }
if ($(e.target).closest(".lookahead-tray").length === 0 &&
$(e.target).closest('.lookahead-tray-link').length === 0) {
closeTray();
}
});
},
afterRender: function () {
var that = this;
this.dbSearchTypeahead = new Components.Typeahead({
el: 'input.search-autocomplete',
source: that.data,
onUpdateEventName: that.onUpdateEventName
});
this.dbSearchTypeahead.render();
},
clearValue: function () {
this.$('.search-autocomplete').val('');
},
cleanup: function () {
$("body").off("click.lookaheadTray");
},
trayIsVisible: function () {
return this.$el.is(":visible");
},
toggleTray: function () {
if (this.trayIsVisible()) {
this.closeTray();
} else {
this.openTray();
}
},
openTray: function () {
var speed = FauxtonAPI.constants.MISC.TRAY_TOGGLE_SPEED;
this.$el.velocity('transition.slideDownIn', speed, function () {
this.$el.find('input').focus();
}.bind(this));
},
closeTray: function () {
var $tray = this.$el;
$tray.velocity("reverse", FauxtonAPI.constants.MISC.TRAY_TOGGLE_SPEED, function () {
$tray.hide();
});
FauxtonAPI.Events.trigger('lookaheadTray:close');
},
onKeyup: function (e) {
if (e.which === 27) {
this.closeTray();
}
}
});
//need to make this into a backbone view...
var routeObjectSpinner;
FauxtonAPI.RouteObject.on('beforeEstablish', function (routeObject) {
if (!routeObject.disableLoader) {
var opts = {
lines: 16, // The number of lines to draw
length: 8, // The length of each line
width: 4, // The line thickness
radius: 12, // The radius of the inner circle
color: '#333', // #rbg or #rrggbb
speed: 1, // Rounds per second
trail: 10, // Afterglow percentage
shadow: false // Whether to render a shadow
};
if (routeObjectSpinner) { return; }
if (!$('.spinner').length) {
$('<div class="spinner"></div>')
.appendTo('#app-container');
}
routeObjectSpinner = new Spinner(opts).spin();
$('.spinner').append(routeObjectSpinner.el);
}
});
var removeRouteObjectSpinner = function () {
if (routeObjectSpinner) {
routeObjectSpinner.stop();
routeObjectSpinner = null;
$('.spinner').remove();
}
};
var removeViewSpinner = function (selector) {
var viewSpinner = viewSpinners[selector];
if (viewSpinner) {
viewSpinner.stop();
$(selector).find('.spinner').remove();
delete viewSpinners[selector];
}
};
var viewSpinners = {};
FauxtonAPI.RouteObject.on('beforeRender', function (routeObject, view, selector) {
removeRouteObjectSpinner();
if (!view.disableLoader) {
var opts = _.extend({
lines: 16, // The number of lines to draw
length: 8, // The length of each line
width: 4, // The line thickness
radius: 12, // The radius of the inner circle
color: '#333', // #rbg or #rrggbb
speed: 1, // Rounds per second
trail: 10, // Afterglow percentage
shadow: false // Whether to render a shadow
}, view.loaderStyles);
var viewSpinner = new Spinner(opts).spin();
$('<div class="spinner"></div>')
.appendTo(selector)
.append(viewSpinner.el);
viewSpinners[selector] = viewSpinner;
}
});
FauxtonAPI.RouteObject.on('afterRender', function (routeObject, view, selector) {
removeViewSpinner(selector);
});
FauxtonAPI.RouteObject.on('viewHasRendered', function (view, selector) {
removeViewSpinner(selector);
removeRouteObjectSpinner();
});
return Components;
});