flectra/addons/website/static/src/js/menu/content.js
2018-07-19 17:04:13 +05:30

801 lines
27 KiB
JavaScript

flectra.define('website.contentMenu', function (require) {
'use strict';
var core = require('web.core');
var Dialog = require('web.Dialog');
var time = require('web.time');
var weContext = require('web_editor.context');
var widget = require('web_editor.widget');
var websiteNavbarData = require('website.navbar');
var websiteRootData = require('website.WebsiteRoot');
var Widget = require('web.Widget');
var _t = core._t;
var qweb = core.qweb;
var PagePropertiesDialog = widget.Dialog.extend({
template: 'website.pagesMenu.page_info',
xmlDependencies: widget.Dialog.prototype.xmlDependencies.concat(
['/website/static/src/xml/website.pageProperties.xml']
),
events: _.extend({}, widget.Dialog.prototype.events, {
'keyup input#page_name': '_onNameChanged',
'keyup input#page_url': '_onUrlChanged',
'change input#create_redirect': '_onCreateRedirectChanged',
}),
/**
* @constructor
* @override
*/
init: function (parent, page_id, options) {
var self = this;
var serverUrl = window.location.origin + "/";
var length_url = serverUrl.length;
var serverUrlTrunc = serverUrl;
if(length_url > 30)
serverUrlTrunc = serverUrl.slice(0,14) + '..' + serverUrl.slice(-14);
this.serverUrl = serverUrl;
this.serverUrlTrunc = serverUrlTrunc;
this.current_page_url = window.location.pathname;
this.page_id = page_id;
var buttons = [
{text: _t("Save"), classes: "btn-primary o_save_button", click: self.save},
{text: _t("Discard"), close: true},
];
if (options.fromPageManagement) {
buttons.push({text: _t("Go To Page"), icon: "fa-globe", classes: "btn-link pull-right", click: function(e){window.location.href = '/' + self.page.url;}});
}
this._super(parent, _.extend({}, {
title: _t("Page Properties"),
size: 'medium',
buttons: buttons,
}, options || {}));
},
/**
* @override
*/
willStart: function () {
var defs = [this._super.apply(this, arguments)];
var self = this;
var context = weContext.get();
defs.push(this._rpc({
model: 'website.page',
method: 'get_page_info',
args: [self.page_id,context.website_id],
kwargs: {
context: context
},
}).then(function (page) {
page[0].url = _.str.startsWith(page[0].url, '/') ? page[0].url.substring(1) : page[0].url;
self.page = page[0];
}));
defs.push(this._rpc({
model: 'website.redirect',
method: 'fields_get',
}).then(function (fields) {
self.fields = fields;
}));
return $.when.apply($, defs);
},
/**
* @override
*/
start: function() {
var self = this;
var context = weContext.get();
this.$(".ask_for_redirect").hide();
this.$(".redirect_type").hide();
this.$(".warn_about_call").hide();
this._getPageDependencies(self.page_id, context)
.then(function (dependencies) {
var dep_text = [];
_.each(dependencies, function(value, index){
if (value.length > 0) {
dep_text.push(value.length + ' ' + index.toLowerCase());
}
});
dep_text = dep_text.join(', ');
$('#dependencies_redirect').html(qweb.render('website.show_page_dependencies', { dependencies: dependencies, dep_text: dep_text }));
$('#dependencies_redirect [data-toggle="popover"]').popover({
container: 'body'
});
});
this._getSupportedMimetype(context)
.then(function (mimetypes) {
self.supportedMimetype = mimetypes;
});
this._getPageKeyDependencies(self.page_id, context)
.then(function (dependencies) {
var dep_text = [];
_.each(dependencies, function(value, index){
if (value.length > 0) {
dep_text.push(value.length + ' ' + index.toLowerCase());
}
});
dep_text = dep_text.join(', ');
$('.warn_about_call').html(qweb.render('website.show_page_key_dependencies', { dependencies: dependencies, dep_text: dep_text }));
$('.warn_about_call [data-toggle="popover"]').popover({
container: 'body'
});
});
var l10n = _t.database.parameters;
var datepickersOptions = {
minDate: moment({ y: 1900 }),
maxDate: moment().add(200, "y"),
calendarWeeks: true,
icons : {
time: 'fa fa-clock-o',
date: 'fa fa-calendar',
next: 'fa fa-chevron-right',
previous: 'fa fa-chevron-left',
up: 'fa fa-chevron-up',
down: 'fa fa-chevron-down',
},
locale : moment.locale(),
format : time.getLangDatetimeFormat(),
widgetPositioning : {
horizontal: 'auto',
vertical: 'top',
},
widgetParent: 'body',
};
if (self.page.date_publish) {
datepickersOptions.defaultDate = time.str_to_datetime(self.page.date_publish);
}
this.$("#date_publish_container").datetimepicker(datepickersOptions);
return this._super.apply(this, arguments);
},
/**
* @override
*/
destroy: function(){
$('.popover').popover('hide');
return this._super.apply(this, arguments);
},
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* @override
*/
save: function(data){
var self = this;
var context = weContext.get();
var url = $(".o_page_management_info #page_url").val();
var $date_publish = $(".o_page_management_info #date_publish");
$date_publish.closest(".form-group").removeClass('has-error');
var date_publish = $date_publish.val();
if (date_publish != "") {
date_publish = this._parse_date(date_publish);
if (!date_publish) {
$date_publish.closest(".form-group").addClass('has-error');
return;
}
}
var params = {
id: self.page.id,
name: $(".o_page_management_info #page_name").val(),
//replace duplicate following "/" by only one "/"
url: url.replace(/\/{2,}/g,"/"),
is_menu: $(".o_page_management_info #is_menu").prop('checked'),
is_homepage: $(".o_page_management_info #is_homepage").prop('checked'),
website_published: $(".o_page_management_info #is_published").prop('checked'),
create_redirect: $(".o_page_management_info #create_redirect").prop('checked'),
redirect_type: $(".o_page_management_info #redirect_type").val(),
website_indexed: $(".o_page_management_info #is_indexed").prop('checked'),
date_publish: date_publish,
};
self._rpc({
model: 'website.page',
method: 'save_page_info',
args: [[context.website_id], params],
kwargs: {
context: context
},
}).then(function (url) {
// If from page manager: reload url, if from page itself: go to (possibly) new url
if (self._getMainObject().model == 'website.page')
window.location.href = url.toLowerCase();
else
window.location.reload(true);
});
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Retrieves the page URL dependencies for the given object id.
*
* @private
* @param {integer} moID
* @param {Object} context
* @returns {Deferred<Array>}
*/
_getPageDependencies: function (moID, context) {
return this._rpc({
model: 'website',
method: 'page_search_dependencies',
args: [moID],
context: context,
});
},
/**
* Retrieves the page's key dependencies for the given object id.
*
* @private
* @param {integer} moID
* @param {Object} context
* @returns {Deferred<Array>}
*/
_getPageKeyDependencies: function (moID, context) {
return this._rpc({
model: 'website',
method: 'page_search_key_dependencies',
args: [moID],
context: context,
});
},
/**
* Retrieves supported mimtype
*
* @private
* @param {Object} context
* @returns {Deferred<Array>}
*/
_getSupportedMimetype: function (context) {
return this._rpc({
model: 'website',
method: 'guess_mimetype',
context: context,
});
},
/**
* Returns information about the page main object.
*
* @private
* @returns {Object} model and id
*/
_getMainObject: function () {
var repr = $('html').data('main-object');
var m = repr.match(/(.+)\((\d+),(.*)\)/);
return {
model: m[1],
id: m[2] | 0,
};
},
/**
* Converts a string representing the browser datetime
* (exemple: Albanian: '2018-Qer-22 15.12.35.')
* to a string representing UTC in Odoo's datetime string format
* (exemple: '2018-04-22 13:12:35').
*
* The time zone of the datetime string is assumed to be the one of the
* browser and it will be converted to UTC (standard for Odoo).
*
* @private
* @param {String} value A string representing a datetime.
* @returns {String|false} A string representing an UTC datetime if the given value is valid, false otherwise.
*/
_parse_date: function (value) {
var datetime = moment(value, time.getLangDatetimeFormat(), true);
if (datetime.isValid()) {
return time.datetime_to_str(datetime.toDate());
}
else {
return false;
}
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
_onUrlChanged: function () {
var url = this.$('input#page_url').val();
if (url != this.page.url) {
this.$(".ask_for_redirect").show();
}
else {
this.$(".ask_for_redirect").hide();
}
},
_onNameChanged: function () {
var name = this.$('input#page_name').val();
//If the file type is a supported mimetype, check if it is t-called. If so, warn user
//Note: different from page_search_dependencies which check only for url and not key
var ext = '.' + this.page.name.split('.').pop();
if(ext in this.supportedMimetype && ext != '.html'){
if (name != this.page.name) {
this.$(".warn_about_call").show();
}
else {
this.$(".warn_about_call").hide();
}
}
},
_onCreateRedirectChanged: function () {
var createRedirect = this.$('input#create_redirect').prop('checked');
if (createRedirect) {
this.$(".redirect_type").show();
}
else {
this.$(".redirect_type").hide();
}
},
});
var MenuEntryDialog = widget.LinkDialog.extend({
xmlDependencies: widget.LinkDialog.prototype.xmlDependencies.concat(
['/website/static/src/xml/website.contentMenu.xml']
),
/**
* @constructor
* @override
*/
init: function (parent, options, editor, data) {
data.text = data.name || '';
data.isNewWindow = data.new_window;
this.data = data;
this.menu_link_options = options.menu_link_options;
this._super(parent, _.extend({}, {
title: _t("Create Menu"),
}, options || {}), editor, data);
},
/**
* @override
*/
start: function () {
var self = this;
this.$('.o_link_dialog_preview').remove();
this.$('.window-new, .link-style').closest('.form-group').remove();
this.$('label[for="o_link_dialog_label_input"]').text(_t("Menu Label"));
if (this.menu_link_options) { // add menu link option only when adding new menu
self.$('#o_link_dialog_url_input').closest('.form-group').hide();
this.$('#o_link_dialog_label_input').closest('.form-group').after(qweb.render('website.contentMenu.dialog.edit.link_menu_options'));
// remove the label that is automatically added before
this.$('#o_link_dialog_url_input').parent().siblings().html('');
this.$('input[name=link_menu_options]').on('change', function () {
self.$('#o_link_dialog_url_input').closest('.form-group').toggle();
});
}
this.$modal.find('.modal-lg').removeClass('modal-lg')
.find('.col-md-8').removeClass('col-md-8').addClass('col-xs-12');
return this._super.apply(this, arguments);
},
/**
* @override
*/
save: function () {
var $e = this.$('#o_link_dialog_label_input');
if (!$e.val() || !$e[0].checkValidity()) {
$e.closest('.form-group').addClass('has-error');
$e.focus();
return;
}
if (this.$('input[name=link_menu_options]:checked').val() === 'new_page') {
window.location = '/website/add/' + encodeURIComponent($e.val()) + '?add_menu=1';
return;
}
return this._super.apply(this, arguments);
},
});
var SelectEditMenuDialog = widget.Dialog.extend({
template: 'website.contentMenu.dialog.select',
xmlDependencies: widget.Dialog.prototype.xmlDependencies.concat(
['/website/static/src/xml/website.contentMenu.xml']
),
/**
* @constructor
* @override
*/
init: function (parent, options) {
var self = this;
self.roots = [{id: null, name: _t("Top Menu")}];
$('[data-content_menu_id]').each(function () {
self.roots.push({id: $(this).data('content_menu_id'), name: $(this).attr('name')});
});
this._super(parent, _.extend({}, {
title: _t("Select a Menu"),
save_text: _t("Continue")
}, options || {}));
},
/**
* @override
*/
save: function () {
this.final_data = parseInt(this.$el.find('select').val() || null);
this._super.apply(this, arguments);
},
});
var EditMenuDialog = widget.Dialog.extend({
template: 'website.contentMenu.dialog.edit',
xmlDependencies: widget.Dialog.prototype.xmlDependencies.concat(
['/website/static/src/xml/website.contentMenu.xml']
),
events: _.extend({}, widget.Dialog.prototype.events, {
'click a.js_add_menu': '_onAddMenuButtonClick',
'click button.js_delete_menu': '_onDeleteMenuButtonClick',
'click button.js_edit_menu': '_onEditMenuButtonClick',
}),
/**
* @constructor
* @override
*/
init: function (parent, options, rootID) {
this._super(parent, _.extend({}, {
title: _t("Edit Menu"),
size: 'medium',
}, options || {}));
this.rootID = rootID;
},
/**
* @override
*/
willStart: function () {
var defs = [this._super.apply(this, arguments)];
var self = this;
var context = weContext.get();
defs.push(this._rpc({
model: 'website.menu',
method: 'get_tree',
args: [context.website_id, this.rootID],
context: context,
}).then(function (menu) {
self.menu = menu;
self.root_menu_id = menu.id;
self.flat = self._flatenize(menu);
self.to_delete = [];
}));
return $.when.apply($, defs);
},
/**
* @override
*/
start: function () {
var r = this._super.apply(this, arguments);
this.$('.oe_menu_editor').nestedSortable({
listType: 'ul',
handle: 'div',
items: 'li',
maxLevels: 2,
toleranceElement: '> div',
forcePlaceholderSize: true,
opacity: 0.6,
placeholder: 'oe_menu_placeholder',
tolerance: 'pointer',
attribute: 'data-menu-id',
expression: '()(.+)', // nestedSortable takes the second match of an expression (*sigh*)
});
return r;
},
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* @override
*/
save: function () {
var _super = this._super.bind(this);
var self = this;
var new_menu = this.$('.oe_menu_editor').nestedSortable('toArray', {startDepthCount: 0});
var levels = [];
var data = [];
var context = weContext.get();
// Resequence, re-tree and remove useless data
new_menu.forEach(function (menu) {
if (menu.id) {
levels[menu.depth] = (levels[menu.depth] || 0) + 1;
var mobj = self.flat[menu.id];
mobj.sequence = levels[menu.depth];
mobj.parent_id = (menu.parent_id|0) || menu.parent_id || self.root_menu_id;
delete(mobj.children);
data.push(mobj);
}
});
this._rpc({
model: 'website.menu',
method: 'save',
args: [[context.website_id], { data: data, to_delete: self.to_delete }],
context: context,
}).then(function () {
return _super();
});
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Returns a mapping id -> menu item containing all the menu items in the
* given menu hierarchy.
*
* @private
* @param {Object} node
* @param {Object} [_dict] internal use: the mapping being built
* @returns {Object}
*/
_flatenize: function (node, _dict) {
_dict = _dict || {};
var self = this;
_dict[node.id] = node;
node.children.forEach(function (child) {
self._flatenize(child, _dict);
});
return _dict;
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* Called when the "add menu" button is clicked -> Opens the appropriate
* dialog to edit this new menu.
*
* @private
*/
_onAddMenuButtonClick: function () {
var self = this;
var dialog = new MenuEntryDialog(this, {menu_link_options: true}, undefined, {});
dialog.on('save', this, function (link) {
var new_menu = {
id: _.uniqueId('new-'),
name: link.text,
url: link.url,
new_window: link.isNewWindow,
parent_id: false,
sequence: 0,
children: [],
};
self.flat[new_menu.id] = new_menu;
self.$('.oe_menu_editor').append(
qweb.render('website.contentMenu.dialog.submenu', { submenu: new_menu }));
});
dialog.open();
},
/**
* Called when the "delete menu" button is clicked -> Deletes this menu.
*
* @private
*/
_onDeleteMenuButtonClick: function (ev) {
var $menu = $(ev.currentTarget).closest('[data-menu-id]');
var menuID = $menu.data('menu-id')|0;
if (menuID) {
this.to_delete.push(menuID);
}
$menu.remove();
},
/**
* Called when the "edit menu" button is clicked -> Opens the appropriate
* dialog to edit this menu.
*
* @private
*/
_onEditMenuButtonClick: function (ev) {
var self = this;
var menu_id = $(ev.currentTarget).closest('[data-menu-id]').data('menu-id');
var menu = self.flat[menu_id];
if (menu) {
var dialog = new MenuEntryDialog(this, {}, undefined, menu);
dialog.on('save', this, function (link) {
var id = link.id;
var menu_obj = self.flat[id];
_.extend(menu_obj, {
'name': link.text,
'url': link.url,
'new_window': link.isNewWindow,
});
var $menu = self.$('[data-menu-id="' + id + '"]');
$menu.find('.js_menu_label').first().text(menu_obj.name);
});
dialog.open();
} else {
Dialog.alert(null, "Could not find menu entry");
}
},
});
var ContentMenu = websiteNavbarData.WebsiteNavbarActionWidget.extend({
xmlDependencies: ['/website/static/src/xml/website.xml'],
actions: _.extend({}, websiteNavbarData.WebsiteNavbarActionWidget.prototype.actions || {}, {
edit_menu: '_editMenu',
page_properties: '_pageProperties',
}),
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Returns information about the page main object.
*
* @private
* @returns {Object} model and id
*/
_getMainObject: function () {
var repr = $('html').data('main-object');
var m = repr.match(/(.+)\((\d+),(.*)\)/);
return {
model: m[1],
id: m[2] | 0,
};
},
//--------------------------------------------------------------------------
// Actions
//--------------------------------------------------------------------------
/**
* Asks the user which menu to edit if multiple menus exist on the page.
* Then opens the menu edition dialog.
* Then executes the given callback once the edition is saved, to finally
* reload the page.
*
* @private
* @param {function} [beforeReloadCallback]
* @returns {Deferred}
* Unresolved if the menu is edited and saved as the page will be
* reloaded.
* Resolved otherwise.
*/
_editMenu: function (beforeReloadCallback) {
var self = this;
var def = $.Deferred();
// If there is multiple menu on the page, ask the user which one he
// wants to edit
var selectDef = $.Deferred();
if ($('[data-content_menu_id]').length) {
var select = new SelectEditMenuDialog(this);
select.on('save', selectDef, selectDef.resolve);
select.on('cancel', def, def.resolve);
select.open();
} else {
selectDef.resolve(null);
}
selectDef.then(function (rootID) {
// Open the dialog to show the menu structure and allow its edition
var editDef = $.Deferred();
var dialog = new EditMenuDialog(self, {}, rootID).open();
dialog.on('save', editDef, editDef.resolve);
dialog.on('cancel', def, def.resolve);
return editDef;
}).then(function () {
// Before reloading the page after menu modification, does the
// given action to do.
return beforeReloadCallback && beforeReloadCallback();
}).then(function () {
// Reload the page so that the menu modification are shown
window.location.reload(true);
});
return def;
},
_pageProperties: function () {
var self = this;
var moID = self._getMainObject().id;
var dialog = new PagePropertiesDialog(self,moID,{}).open();
return dialog;
},
});
var PageManagement = Widget.extend({
xmlDependencies: ['/website/static/src/xml/website.xml'],
events: {
'click a.js_page_properties': '_onPagePropertiesButtonClick',
'click a.js_clone_page': '_onClonePageButtonClick',
'click a.js_delete_page': '_onDeletePageButtonClick',
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Retrieves the page dependencies for the given object id.
*
* @private
* @param {integer} moID
* @param {Object} context
* @returns {Deferred<Array>}
*/
_getPageDependencies: function (moID, context) {
return this._rpc({
model: 'website',
method: 'page_search_dependencies',
args: [moID],
context: context,
});
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
_onPagePropertiesButtonClick: function (ev) {
var moID = $(ev.currentTarget).data('id');
var dialog = new PagePropertiesDialog(this,moID, {'fromPageManagement': true}).open();
return dialog;
},
_onClonePageButtonClick: function (ev) {
var pageId = $(ev.currentTarget).data('id');
var context = weContext.get();
this._rpc({
model: 'website.page',
method: 'clone_page',
args: [pageId],
kwargs: {
context: context,
},
}).then(function (path) {
window.location.href = path;
});
},
_onDeletePageButtonClick: function (ev) {
var pageId = $(ev.currentTarget).data('id');
var self = this;
var context = weContext.get();
var def = $.Deferred();
// Search the page dependencies
this._getPageDependencies(pageId, context)
.then(function (dependencies) {
// Inform the user about those dependencies and ask him confirmation
var confirmDef = $.Deferred();
Dialog.safeConfirm(self, "", {
title: _t("Delete Page"),
$content: $(qweb.render('website.delete_page', {dependencies: dependencies})),
confirm_callback: confirmDef.resolve.bind(confirmDef),
cancel_callback: def.resolve.bind(self),
});
return confirmDef;
}).then(function () {
// Delete the page if the user confirmed
return self._rpc({
model: 'website.page',
method: 'delete_page',
args: [pageId],
context: context,
});
}).then(function () {
window.location.reload(true);
}, def.reject.bind(def));
},
});
websiteNavbarData.websiteNavbarRegistry.add(ContentMenu, '#content-menu');
websiteRootData.websiteRootRegistry.add(PageManagement, '#edit_website_pages');
return {
PagePropertiesDialog: PagePropertiesDialog,
ContentMenu: ContentMenu,
EditMenuDialog: EditMenuDialog,
MenuEntryDialog: MenuEntryDialog,
SelectEditMenuDialog: SelectEditMenuDialog,
};
});