[REF] website_slides: refactor the category add modal on frontend

Purpose
=======

This commits refactors the way slide.category are added on frontend:

 * JS now extends a standard Dialog widget;
 * added "required" name value on the form and handled validation;
 * controller adds the category at the bottom of the list (sequence+1);

Commit linked to task ID 1942852 and PR #31394.
This commit is contained in:
Aurélien Warnon
2019-02-26 16:07:33 +00:00
committed by Thibault Delavallée
parent 5db9ed0391
commit 0e73de5dea
7 changed files with 135 additions and 198 deletions
+33 -19
View File
@@ -576,6 +576,39 @@ class WebsiteSlides(WebsiteProfile):
result = slide._send_share_email(email)
return result
# --------------------------------------------------
# CATEGORY MANAGEMENT
# --------------------------------------------------
@http.route(['/slides/category/search_read'], type='json', auth='user', methods=['POST'], website=True)
def slide_category_search_read(self, fields, domain):
can_create = request.env['slide.category'].check_access_rights('create', raise_exception=False)
return {
'read_results': request.env['slide.category'].search_read(domain, fields),
'can_create': can_create,
}
@http.route('/slides/category/add', type="http", website=True, auth="user")
def slide_category_add(self, channel_id, name):
""" Adds a category to the specified channel. If categories already exist
within this channel, it will be added at the bottom (sequence+1) """
channel = request.env['slide.channel'].browse(int(channel_id))
values = {
'name': name,
'channel_id': channel.id
}
latest_category = request.env['slide.category'].search_read([
('channel_id', '=', channel.id)
], ["sequence"], order="sequence desc", limit=1)
if latest_category:
values['sequence'] = latest_category[0]['sequence'] + 1
request.env['slide.category'].create(values)
return werkzeug.utils.redirect("/slides/%s" % (slug(channel)))
# --------------------------------------------------
# TOOLS
# --------------------------------------------------
@@ -694,24 +727,6 @@ class WebsiteSlides(WebsiteProfile):
'can_create': can_create,
}
@http.route(['/slides/category/search_read'], type='json', auth='user', methods=['POST'], website=True)
def slide_category_search_read(self, fields, domain):
can_create = request.env['slide.category'].check_access_rights('create', raise_exception=False)
return {
'read_results': request.env['slide.category'].search_read(domain, fields),
'can_create': can_create,
}
@http.route('/slides/add_category', type="json", website=True, auth="user")
def add_category(self, channel_id, name, **kw):
channel = request.env['slide.channel'].browse(channel_id)
request.env['slide.category'].create({
'name': name,
'channel_id': channel.id
})
return {'url': "/slides/%s" %(slug(channel))}
#Not using the /web/dataset/resequence route as a slide can be dragged into another category at the same time
@http.route('/slides/resequence_slides', type="json", website=True, auth="user")
def resequence_slides(self, slides_data=None, **kw):
@@ -727,7 +742,6 @@ class WebsiteSlides(WebsiteProfile):
'category_id': slide['category_id']
})
# --------------------------------------------------
# EMBED IN THIRD PARTY WEBSITES
# --------------------------------------------------
@@ -1,148 +0,0 @@
odoo.define('website_slides.add.section', function (require) {
'use strict';
var sAnimations = require('website.content.snippets.animation');
var core = require('web.core');
var Widget = require('web.Widget');
var _t = core._t;
var SectionDialog = Widget.extend({
template: 'website.slide.add.section',
events: {
'hidden.bs.modal': 'destroy',
'click button.save': '_save',
'click button[data-dismiss="modal"]': '_cancel',
'change input#upload': '_slideUpload',
'change input#url': '_slideUrl',
'click .list-group-item': function (ev) {
this.$('.list-group-item').removeClass('active');
$(ev.target).closest('li').addClass('active');
}
},
/**
* @override
* @param {Object} el
* @param {number} channel_id
*/
init: function (el, channelID) {
this._super(el, channelID);
this.channel_id = parseInt(channelID, 10);
this.index_content = '';
},
/**
* @override
*/
start: function () {
this.$el.modal({
backdrop: 'static'
});
return this._super.apply(this, arguments);
},
_getValue: function () {
var canvas = this.$('#data_canvas')[0],
values = {
'channel_id': this.channel_id || '',
'url': this.$('#url').val(),
'name': this.$('#section_name').val()
};
return values;
},
/**
* @private
*/
_validate: function () {
this.$('.form-group').removeClass('o_has_error').find('.form-control, .custom-select').removeClass('is-invalid');
if (!this.$('#name').val()) {
this.$('#name').closest('.form-group').addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid');
return false;
}
var url = this.$('#url').val() ? this.is_valid_url : false;
if (!(this.file.name || url)) {
this.$('#url').closest('.form-group').addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid');
return false;
}
return true;
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @override
* @param {Object} ev
*/
_save: function (ev) {
var self = this;
var values = this._getValue();
if ($(ev.target).data('published')) {
values.website_published = true;
}
this.$('.oe_slides_upload_loading').show();
this.$('.modal-footer, .modal-body').hide();
this._rpc({
route: '/slides/add_category',
params: values,
}).then(function (data) {
if (data.error) {
self._displayAlert(data.error);
self.$('.oe_slides_upload_loading').hide();
self.$('.modal-footer, .modal-body').show();
} else {
window.location = data.url;
}
});
},
/**
* @override
*/
_cancel: function () {
this.trigger('cancel');
},
});
sAnimations.registry.websiteSlidesSection = sAnimations.Class.extend({
selector: '.oe_slide_js_add_section',
xmlDependencies: ['/website_slides/static/src/xml/website_slides_upload.xml'],
read_events: {
'click': '_onAddSectionClick',
},
/**
* @override
*/
start: function () {
// Automatically open the upload dialog if requested from query string
if ($.deparam.querystring().enable_slide_upload !== undefined) {
this._openDialog(this.$el.attr('channel_id'));
}
return this._super.apply(this, arguments);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
_openDialog: function (channelID) {
new SectionDialog(this, channelID).appendTo(document.body);
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
* @param {Event} ev
*/
_onAddSectionClick: function (ev) {
console.log('test');
this._openDialog($(ev.currentTarget).attr('channel_id'));
},
});
});
@@ -0,0 +1,84 @@
odoo.define('website_slides.category.add', function (require) {
'use strict';
var sAnimations = require('website.content.snippets.animation');
var Dialog = require('web.Dialog');
var core = require('web.core');
var _t = core._t;
var CategoryAddDialog = Dialog.extend({
template: 'slides.category.add',
/**
* @override
*/
init: function (parent, options) {
options = _.defaults(options || {}, {
title: _t('Add a section'),
size: 'medium',
buttons: [{
text: _t('Save'),
classes: 'btn-primary',
click: this._onClickFormSubmit.bind(this)
}, {
text: _t('Discard'),
close: true
}]
});
this.channelId = options.channelId;
this._super(parent, options);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
_formValidate: function ($form) {
$form.addClass('was-validated');
return $form[0].checkValidity();
},
_onClickFormSubmit: function (ev) {
var $form = this.$('#slide_category_add_form');
if (this._formValidate($form)) {
$form.submit();
}
},
});
sAnimations.registry.websiteSlidesCategoryAdd = sAnimations.Class.extend({
selector: '.o_wslides_js_slide_section_add',
xmlDependencies: ['/website_slides/static/src/xml/slide_management.xml'],
read_events: {
'click': '_onAddSectionClick',
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
_openDialog: function (channelId) {
new CategoryAddDialog(this, {channelId: channelId}).open();
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
* @param {Event} ev
*/
_onAddSectionClick: function (ev) {
ev.preventDefault();
this._openDialog($(ev.currentTarget).attr('channel_id'));
},
});
return {
categoryAddDialog: CategoryAddDialog,
websiteSlidesCategoryAdd: sAnimations.registry.websiteSlidesCategoryAdd
};
});
@@ -5,4 +5,20 @@
<p>Are you sure you want to archive this slide ?</p>
</div>
</t>
<t t-name="slides.category.add">
<div>
<form action="/slides/category/add" method="POST" id="slide_category_add_form">
<input type="hidden" name="csrf_token" t-att-value="csrf_token"/>
<input type="hidden" name="channel_id" t-att-value="widget.channelId"/>
<div class="form-group row">
<label for="section_name" class="col-sm-3 col-form-label">Section name</label>
<div class="col-sm-9">
<input type="text" class="form-control" name="name" id="section_name" required="required"/>
</div>
</div>
</form>
</div>
</t>
</templates>
@@ -207,33 +207,4 @@
</div>
</div>
</t>
<t t-name="website.slide.add.section">
<div role="dialog" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<header class="modal-header">
<h3 class="modal-title">Add a section</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">×</button>
</header>
<main class="modal-body modal-content">
<form class="clearfix">
<div class="form-group row">
<label for="section_name" class="col-form-label col-md-3">Section name</label>
<div class="col-md-9">
<input type="text" class="form-control" id="section_name"/>
</div>
</div>
<canvas id="data_canvas" style="display: none;"></canvas>
</form>
</main>
<footer class="modal-footer">
<button type="button" data-loading-text="Loading..." data-published="true" class="btn btn-primary save">Add</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Discard</button>
</footer>
</div>
</div>
</div>
</t>
</templates>
+1 -1
View File
@@ -17,7 +17,7 @@
<script type="text/javascript" src="/website_slides/static/src/js/slides_like.js"/>
<script type="text/javascript" src="/website_slides/static/src/js/slides_share.js"/>
<script type="text/javascript" src="/website_slides/static/src/js/slides_upload.js"/>
<script type="text/javascript" src="/website_slides/static/src/js/slides_category.js"/>
<script type="text/javascript" src="/website_slides/static/src/js/slides_category_add.js"/>
<script type="text/javascript" src="/website_slides/static/src/js/slides_slide_archive.js"/>
<script type="text/javascript" src="/website_slides/static/src/js/slides_course_slides_list.js"/>
<script type="text/javascript" src="/website_slides/static/src/js/slides_course_progress_bar.js"/>
@@ -305,7 +305,7 @@
t-att-data-channel-id="channel.id"
t-att-data-can-upload="channel.can_upload"
t-att-data-can-publish="channel.can_publish">Add Content</a>
<a class="oe_slide_js_add_section" t-attf-channel_id="#{channel.id}" href="#"
<a class="o_wslides_js_slide_section_add" t-attf-channel_id="#{channel.id}" href="#"
groups="website.group_website_publisher">Add Section</a>
</div>
</div>