First commit waiting for Budget Alert
This commit is contained in:
137
sophal/js/widget/widget.dialog.js
Normal file
137
sophal/js/widget/widget.dialog.js
Normal file
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
(function() {
|
||||
|
||||
"use strict";
|
||||
|
||||
var ui = angular.module('axelor.ui');
|
||||
|
||||
ui.directive('uiDialog', function() {
|
||||
return {
|
||||
restrict: 'EA',
|
||||
link: function(scope, element, attrs) {
|
||||
|
||||
var onBeforeClose = scope.$eval(attrs.onBeforeClose);
|
||||
|
||||
var onOpen = scope.$eval(attrs.onOpen);
|
||||
var onClose = scope.$eval(attrs.onClose);
|
||||
var onOK = scope.$eval(attrs.onOk);
|
||||
var cssClass = attrs.css;
|
||||
var buttons = scope.$eval(attrs.buttons) || [];
|
||||
|
||||
if(_.isEmpty(buttons) || (_.isUndefined(onClose) || _.isFunction(onClose))) {
|
||||
buttons.push({
|
||||
text: _t('Close'),
|
||||
'class': 'btn button-close',
|
||||
click: function() {
|
||||
element.dialog('close');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(_.isEmpty(buttons) || _.isUndefined(onOK) || _.isFunction(onOK)){
|
||||
buttons.push({
|
||||
text: _t('OK'),
|
||||
'class': 'btn btn-primary button-ok',
|
||||
click: function() {
|
||||
if (onOK) {
|
||||
onOK();
|
||||
}
|
||||
else
|
||||
element.dialog('close');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var dialog = element.dialog({
|
||||
dialogClass: 'ui-dialog-responsive ' + (cssClass || ''),
|
||||
resizable: false,
|
||||
draggable: false,
|
||||
autoOpen: false,
|
||||
closeOnEscape: true,
|
||||
modal: true,
|
||||
zIndex: 1100,
|
||||
show: {
|
||||
effect: 'fade',
|
||||
duration: 300
|
||||
},
|
||||
buttons: buttons
|
||||
});
|
||||
|
||||
// fix IE11 issue
|
||||
if (axelor.browser.msie && axelor.browser.rv) {
|
||||
var headerHeight = 46;
|
||||
var footerHeight = 52;
|
||||
var dialogMargin = 64;
|
||||
|
||||
function onResize() {
|
||||
var availableHeight = $(window).height() - headerHeight - footerHeight - dialogMargin - 8;
|
||||
var contentHeight = element.children().height();
|
||||
var myHeight = Math.min(availableHeight, contentHeight);
|
||||
element.height(myHeight);
|
||||
}
|
||||
|
||||
dialog.on('dialogopen', function (e, ui) {
|
||||
$(window).on('resize', onResize);
|
||||
setTimeout(onResize);
|
||||
});
|
||||
dialog.on('dialogclose', function (e, ui) {
|
||||
$(window).off('resize', onResize);
|
||||
});
|
||||
scope.$on('$destroy', function() {
|
||||
$(window).off('resize', onResize);
|
||||
});
|
||||
|
||||
element.addClass('ui-dialog-ie11');
|
||||
}
|
||||
|
||||
// focus the previous visible dialog
|
||||
dialog.on('dialogclose', function(e, ui){
|
||||
var target = element.data('$target');
|
||||
if (target) {
|
||||
return setTimeout(function(){
|
||||
if (!axelor.device.mobile) {
|
||||
var input = target.find(':input:first');
|
||||
input.addClass('x-focus').focus().select();
|
||||
setTimeout(function () {
|
||||
input.removeClass('x-focus');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
$('body .ui-dialog:visible:last').focus();
|
||||
});
|
||||
|
||||
dialog.on('dialogopen', onOpen)
|
||||
.on('dialogclose', onClose)
|
||||
.on('dialogbeforeclose', onBeforeClose);
|
||||
|
||||
scope.$on('$destroy', function(){
|
||||
if (dialog) {
|
||||
if (dialog.data('dialog')) {
|
||||
dialog.dialog('destroy');
|
||||
}
|
||||
dialog.remove();
|
||||
dialog = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
})();
|
||||
285
sophal/js/widget/widget.menubar.js
Normal file
285
sophal/js/widget/widget.menubar.js
Normal file
@ -0,0 +1,285 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
(function() {
|
||||
|
||||
"use strict";
|
||||
|
||||
var ui = angular.module('axelor.ui');
|
||||
|
||||
MenuBarCtrl.$inject = ['$scope', '$element'];
|
||||
function MenuBarCtrl($scope, $element) {
|
||||
|
||||
this.isDivider = function(item) {
|
||||
return !item.title && !item.icon;
|
||||
};
|
||||
|
||||
this.isSubMenu = function(item) {
|
||||
return item && item.items && item.items.length > 0;
|
||||
};
|
||||
|
||||
$scope.isImage = function (menu) {
|
||||
return menu.icon && menu.icon.indexOf('fa-') !== 0;
|
||||
};
|
||||
|
||||
$scope.isIcon = function (menu) {
|
||||
return menu.icon && menu.icon.indexOf('fa-') === 0;
|
||||
};
|
||||
|
||||
$scope.canShowTitle = function(menu) {
|
||||
return menu.showTitle === null || menu.showTitle === undefined || menu.showTitle;
|
||||
};
|
||||
}
|
||||
|
||||
ui.directive('uiMenuBar', function() {
|
||||
|
||||
return {
|
||||
replace: true,
|
||||
controller: MenuBarCtrl,
|
||||
scope: {
|
||||
menus: '=',
|
||||
handler: '='
|
||||
},
|
||||
link: function(scope, element, attrs, ctrl) {
|
||||
|
||||
ctrl.handler = scope.handler;
|
||||
|
||||
scope.onMenuClick = _.once(function onMenuClick(e) {
|
||||
element.find('.dropdown-toggle').dropdown();
|
||||
$(e.currentTarget).dropdown('toggle');
|
||||
});
|
||||
},
|
||||
|
||||
template:
|
||||
"<ul class='nav menu-bar'>" +
|
||||
"<li class='menu dropdown button-menu' ng-class='::{\"button-menu\": menu.isButton, \"has-icon\": menu.icon}' ng-repeat='menu in ::menus'>" +
|
||||
"<a href='' class='dropdown-toggle btn' ng-class='::{\"btn\": menu.isButton}' data-toggle='dropdown' ng-click='onMenuClick($event)'>" +
|
||||
"<img ng-if='::isImage(menu)' ng-src='{{menu.icon}}'> " +
|
||||
"<i class='fa {{::menu.icon}}' ng-if='::isIcon(menu)'></i> " +
|
||||
"<span class='menu-title' ng-show='::canShowTitle(menu)'>{{::menu.title}}</span> " +
|
||||
"<b class='caret'></b>" +
|
||||
"</a>" +
|
||||
"<ul ui-menu='menu'></ul>" +
|
||||
"</li>" +
|
||||
"</ul>"
|
||||
};
|
||||
});
|
||||
|
||||
ui.directive('uiMenu', function() {
|
||||
|
||||
return {
|
||||
replace: true,
|
||||
require: '^uiMenuBar',
|
||||
scope: {
|
||||
menu: '=uiMenu'
|
||||
},
|
||||
link: function(scope, element, attrs, ctrl) {
|
||||
|
||||
},
|
||||
template:
|
||||
"<ul class='dropdown-menu'>" +
|
||||
"<li ng-repeat='item in ::menu.items' ui-menu-item='item'>" +
|
||||
"</ul>"
|
||||
};
|
||||
});
|
||||
|
||||
ui.directive('uiMenuItem', ['$compile', 'ActionService', function($compile, ActionService) {
|
||||
|
||||
return {
|
||||
replace: true,
|
||||
require: '^uiMenuBar',
|
||||
scope: {
|
||||
item: '=uiMenuItem'
|
||||
},
|
||||
link: function(scope, element, attrs, ctrl) {
|
||||
|
||||
var item = scope.item;
|
||||
var handler = null;
|
||||
|
||||
scope.field = item;
|
||||
scope.isDivider = ctrl.isDivider(item);
|
||||
scope.isSubMenu = ctrl.isSubMenu(item);
|
||||
|
||||
if (item.action) {
|
||||
handler = ActionService.handler(ctrl.handler, element, {
|
||||
action: item.action,
|
||||
prompt: item.prompt
|
||||
});
|
||||
element.addClass("action-item").attr("x-name", item.name);
|
||||
}
|
||||
|
||||
scope.isRequired = function(){};
|
||||
scope.isValid = function(){};
|
||||
|
||||
attrs = {
|
||||
hidden: !!item.hidden,
|
||||
readonly: !!item.readonly
|
||||
};
|
||||
|
||||
scope.attr = function(name, value) {
|
||||
attrs[name] = value;
|
||||
};
|
||||
|
||||
scope.isReadonly = function(){
|
||||
if (attrs.readonly) return true;
|
||||
if (_.isFunction(item.active)) {
|
||||
return !item.active();
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
scope.isHidden = function(){
|
||||
if (attrs.hidden) return true;
|
||||
if (_.isFunction(item.visible)) {
|
||||
return !item.visible();
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
var form = element.parents('.form-view:first');
|
||||
var formScope = form.data('$scope');
|
||||
|
||||
if (formScope) {
|
||||
formScope.$watch('record', function menubarRecordWatch(rec) {
|
||||
scope.record = rec;
|
||||
});
|
||||
}
|
||||
|
||||
scope.onClick = function(e) {
|
||||
element.parents('.dropdown').dropdown('toggle');
|
||||
if (scope.isSubMenu) return;
|
||||
if (item.action) {
|
||||
return handler.onClick();
|
||||
}
|
||||
if (_.isFunction(item.click)) {
|
||||
return item.click(e);
|
||||
}
|
||||
};
|
||||
|
||||
scope.cssClass = function() {
|
||||
if (scope.isDivider) {
|
||||
return 'divider';
|
||||
}
|
||||
if (scope.isSubMenu) {
|
||||
return 'dropdown-submenu';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if (scope.isSubMenu) {
|
||||
$compile('<ul ui-menu="item"></ul>')(scope, function(cloned, scope) {
|
||||
element.append(cloned);
|
||||
});
|
||||
}
|
||||
},
|
||||
template:
|
||||
"<li ng-class='cssClass()' ui-widget-states ng-show='!isHidden()'>" +
|
||||
"<a href='' ng-show='isReadonly()' class='disabled'>{{item.title}}</a>" +
|
||||
"<a href='' ng-show='!isDivider && !isReadonly()' ng-click='onClick($event)'>{{item.title}}</a>" +
|
||||
"</li>"
|
||||
};
|
||||
}]);
|
||||
|
||||
ui.directive('uiToolbarAdjust', function() {
|
||||
|
||||
return function (scope, element, attrs) {
|
||||
|
||||
var elemMenubar = null;
|
||||
var elemToolbar = null;
|
||||
var elemSiblings = null;
|
||||
|
||||
var elemToolbarMobile = null;
|
||||
|
||||
function setup() {
|
||||
elemMenubar = element.children('.view-menubar');
|
||||
elemToolbar = element.children('.view-toolbar');
|
||||
elemSiblings = element.children(':not(.view-menubar,.view-toolbar,.view-toolbar-mobile)');
|
||||
|
||||
elemToolbarMobile = element.children('.view-toolbar-mobile').hide();
|
||||
|
||||
var running = false;
|
||||
scope.$onAdjust(function () {
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
running = true;
|
||||
try {
|
||||
adjust();
|
||||
} finally {
|
||||
running = false;
|
||||
}
|
||||
});
|
||||
|
||||
scope.$callWhen(adjust, function () {
|
||||
return element.is(':visible');
|
||||
});
|
||||
}
|
||||
|
||||
function hideAndShow(first, second, visibility) {
|
||||
[elemMenubar, elemToolbar, elemToolbarMobile].forEach(function (elem) {
|
||||
elem.hide().css('visibility', 'hidden');
|
||||
});
|
||||
[first, second].forEach(function (elem) {
|
||||
if (elem && element.is(':visible')) {
|
||||
elem.show().css('visibility', visibility || '');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function adjust() {
|
||||
|
||||
if (elemMenubar === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var width = element.width() - 8;
|
||||
elemSiblings.each(function (i) {
|
||||
width -= $(this).width();
|
||||
});
|
||||
|
||||
if (axelor.device.small) {
|
||||
if (width > elemToolbarMobile.width() + elemMenubar.width()) {
|
||||
hideAndShow(elemToolbarMobile, elemMenubar);
|
||||
} else if (width > elemToolbarMobile.width()) {
|
||||
hideAndShow(elemToolbarMobile);
|
||||
} else if (width > elemMenubar.width()) {
|
||||
hideAndShow(elemMenubar);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
function canShow(first, second) {
|
||||
hideAndShow(first, second, 'hidden');
|
||||
if (width > first.width() + second.width()) {
|
||||
first.css('visibility', '');
|
||||
second.css('visibility', '');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
canShow(elemToolbar, elemMenubar) ||
|
||||
canShow(elemToolbarMobile, elemMenubar) ||
|
||||
canShow(elemToolbar, $()) ||
|
||||
canShow(elemToolbarMobile, $());
|
||||
}
|
||||
|
||||
scope.waitForActions(setup, 100);
|
||||
};
|
||||
});
|
||||
|
||||
})();
|
||||
447
sophal/js/widget/widget.navmenu.js
Normal file
447
sophal/js/widget/widget.navmenu.js
Normal file
@ -0,0 +1,447 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
(function() {
|
||||
|
||||
"use strict";
|
||||
|
||||
var ui = angular.module('axelor.ui');
|
||||
|
||||
NavMenuCtrl.$inject = ['$scope', '$element', 'MenuService', 'NavService'];
|
||||
function NavMenuCtrl($scope, $element, MenuService, NavService) {
|
||||
|
||||
$scope.menus = []; // the first four visible menus
|
||||
$scope.more = []; // rest of the menus
|
||||
|
||||
var hasSideBar = axelor.config['view.menubar.location'] !== 'top';
|
||||
|
||||
MenuService.all().then(function(response) {
|
||||
var res = response.data,
|
||||
data = res.data;
|
||||
|
||||
var items = {};
|
||||
var all = [];
|
||||
|
||||
_.each(data, function(item) {
|
||||
items[item.name] = item;
|
||||
if (item.children === undefined) {
|
||||
item.children = [];
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
_.each(data, function(item) {
|
||||
|
||||
if (hasSideBar && !item.top) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!item.parent) {
|
||||
return all.push(item);
|
||||
}
|
||||
var parent = items[item.parent];
|
||||
if (parent) {
|
||||
parent.children.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.menus = all;
|
||||
$scope.more = all;
|
||||
|
||||
$scope.extra = {
|
||||
title: 'More',
|
||||
children: $scope.more
|
||||
};
|
||||
});
|
||||
|
||||
this.isSubMenu = function(item) {
|
||||
return item && item.children && item.children.length > 0;
|
||||
};
|
||||
|
||||
this.onItemClick = function(item) {
|
||||
if (item.action && !this.isSubMenu(item)) {
|
||||
NavService.openTabByName(item.action);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.hasImage = function (menu) {
|
||||
return menu.icon && menu.icon.indexOf('fa-') !== 0 && menu.icon.indexOf('empty') != -1;
|
||||
};
|
||||
|
||||
$scope.hasIcon = function (menu) {
|
||||
return menu.icon && menu.icon.indexOf('fa-') === 0 && menu.icon.indexOf('empty') != -1;
|
||||
};
|
||||
|
||||
$scope.hasText = function (menu) {
|
||||
return !menu.icon || menu.icon.indexOf('empty') === -1;
|
||||
};
|
||||
}
|
||||
|
||||
ui.directive('navMenuBar', function() {
|
||||
|
||||
return {
|
||||
|
||||
replace: true,
|
||||
|
||||
controller: NavMenuCtrl,
|
||||
|
||||
scope: true,
|
||||
|
||||
link: function(scope, element, attrs, ctrl) {
|
||||
|
||||
var elemTop,
|
||||
elemSub,
|
||||
elemMore;
|
||||
|
||||
var siblingsWidth = 0;
|
||||
var adjusting = false;
|
||||
|
||||
element.hide();
|
||||
|
||||
function adjust() {
|
||||
|
||||
if (adjusting) {
|
||||
return;
|
||||
}
|
||||
|
||||
adjusting = true;
|
||||
|
||||
var count = 0;
|
||||
var parentWidth = element.parent().width() - 32;
|
||||
|
||||
elemMore.hide();
|
||||
elemTop.hide();
|
||||
elemSub.hide();
|
||||
|
||||
while (count < elemTop.length) {
|
||||
var elem = $(elemTop[count]).show();
|
||||
var width = siblingsWidth + element.width();
|
||||
if (width > parentWidth) {
|
||||
elem.hide();
|
||||
|
||||
// show more...
|
||||
elemMore.show();
|
||||
width = siblingsWidth + element.width();
|
||||
if (width > parentWidth) {
|
||||
count--;
|
||||
$(elemTop[count]).hide();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count === elemTop.length) {
|
||||
elemMore.hide();
|
||||
}
|
||||
while(count < elemTop.length) {
|
||||
$(elemSub[count++]).show();
|
||||
}
|
||||
|
||||
adjusting = false;
|
||||
}
|
||||
|
||||
function setup() {
|
||||
|
||||
element.siblings().each(function () {
|
||||
siblingsWidth += $(this).width();
|
||||
});
|
||||
|
||||
elemTop = element.find('.nav-menu.dropdown:not(.nav-menu-more)');
|
||||
elemMore = element.find('.nav-menu.dropdown.nav-menu-more');
|
||||
elemSub = elemMore.find('.dropdown-menu:first > .dropdown-submenu');
|
||||
|
||||
element.show();
|
||||
adjust();
|
||||
|
||||
$(window).on("resize.menubar", adjust);
|
||||
}
|
||||
|
||||
element.on('$destroy', function () {
|
||||
if (element) {
|
||||
$(window).off("resize.menubar");
|
||||
element = null;
|
||||
}
|
||||
});
|
||||
|
||||
var unwatch = scope.$watch('menus', function navMenusWatch(menus, old) {
|
||||
if (!menus || menus.length === 0 || menus === old) {
|
||||
return;
|
||||
}
|
||||
unwatch();
|
||||
setTimeout(setup, 100);
|
||||
});
|
||||
},
|
||||
|
||||
template:
|
||||
"<ul class='nav nav-menu-bar'>" +
|
||||
"<li class='nav-menu dropdown' ng-class='{empty: !hasText(menu)}' ng-repeat='menu in menus track by menu.name'>" +
|
||||
"<a href='javascript:' class='dropdown-toggle' data-toggle='dropdown'>" +
|
||||
"<img ng-if='hasImage(menu)' ng-src='{{menu.icon}}'> " +
|
||||
"<i ng-if='hasIcon(menu)' class='fa {{menu.icon}}'></i> " +
|
||||
"<span ng-if='hasText(menu)' ng-bind='menu.title'></span> " +
|
||||
"<b class='caret'></b>" +
|
||||
"</a>" +
|
||||
"<ul nav-menu='menu'></ul>" +
|
||||
"</li>" +
|
||||
"<li class='nav-menu nav-menu-more dropdown' style='display: none;'>" +
|
||||
"<a href='javascript:' class='dropdown-toggle' data-toggle='dropdown'>" +
|
||||
"<span x-translate>More</span>" +
|
||||
"<b class='caret'></b>" +
|
||||
"</a>" +
|
||||
"<ul nav-menu='extra'></ul>" +
|
||||
"</li>" +
|
||||
"</ul>"
|
||||
};
|
||||
});
|
||||
|
||||
ui.directive('navMenu', function() {
|
||||
|
||||
return {
|
||||
replace: true,
|
||||
require: '^navMenuBar',
|
||||
scope: {
|
||||
menu: '=navMenu'
|
||||
},
|
||||
link: function(scope, element, attrs, ctrl) {
|
||||
|
||||
},
|
||||
template:
|
||||
"<ul class='dropdown-menu'>" +
|
||||
"<li ng-repeat='item in menu.children track by item.name' nav-menu-item='item'>" +
|
||||
"</ul>"
|
||||
};
|
||||
});
|
||||
|
||||
ui.directive('navMenuItem', ['$compile', function($compile) {
|
||||
|
||||
return {
|
||||
replace: true,
|
||||
require: '^navMenuBar',
|
||||
scope: {
|
||||
item: '=navMenuItem'
|
||||
},
|
||||
link: function(scope, element, attrs, ctrl) {
|
||||
|
||||
var item = scope.item;
|
||||
|
||||
scope.isSubMenu = ctrl.isSubMenu(item);
|
||||
scope.isActionMenu = !!item.action;
|
||||
|
||||
scope.onClick = function (e, item) {
|
||||
ctrl.onItemClick(item);
|
||||
};
|
||||
|
||||
if (ctrl.isSubMenu(item)) {
|
||||
element.addClass("dropdown-submenu");
|
||||
$compile('<ul nav-menu="item"></ul>')(scope, function(cloned, scope) {
|
||||
element.append(cloned);
|
||||
});
|
||||
}
|
||||
},
|
||||
template:
|
||||
"<li>" +
|
||||
"<a href='javascript:' ng-click='onClick($event, item)'>{{item.title}}</a>" +
|
||||
"</li>"
|
||||
};
|
||||
}]);
|
||||
|
||||
ui.directive('navMenuFav', function() {
|
||||
|
||||
return {
|
||||
replace: true,
|
||||
controller: ['$scope', '$location', 'DataSource', 'NavService', function ($scope, $location, DataSource, NavService) {
|
||||
|
||||
var ds = DataSource.create("com.axelor.meta.db.MetaMenu", {
|
||||
domain: "self.user = :__user__ and self.link is not null"
|
||||
});
|
||||
|
||||
$scope.items = [];
|
||||
|
||||
function update() {
|
||||
ds.search({
|
||||
fields: ["id", "name", "title", "link"],
|
||||
sortBy: ["-priority"]
|
||||
}).success(function (records, page) {
|
||||
$scope.items = records;
|
||||
});
|
||||
}
|
||||
|
||||
function add(values, callback) {
|
||||
var item = _.findWhere($scope.items, { link: values.link });
|
||||
if (item && item.title === values.title) {
|
||||
return callback();
|
||||
}
|
||||
if (item) {
|
||||
item.title = values.title;
|
||||
} else {
|
||||
item = values;
|
||||
item.name = values.link;
|
||||
item.user = {
|
||||
id: axelor.config['user.id']
|
||||
};
|
||||
item.hidden = true;
|
||||
}
|
||||
|
||||
ds.save(item).success(update).then(callback, callback);
|
||||
}
|
||||
|
||||
$scope.addFav = function () {
|
||||
|
||||
var link = $location.path();
|
||||
if (link === "/") {
|
||||
return;
|
||||
}
|
||||
|
||||
var tab = NavService.getSelected() || {};
|
||||
var vs = tab.$viewScope || {};
|
||||
var title = tab.title || (vs.schema || {}).title || "";
|
||||
|
||||
if (vs.record && vs.record.id > 0) {
|
||||
title = title + " (" + vs.record.id + ")";
|
||||
}
|
||||
|
||||
var item = _.findWhere($scope.items, { link: link });
|
||||
if (item) {
|
||||
title = item.title;
|
||||
}
|
||||
|
||||
var dialog = axelor.dialogs.box("<input type='text' style='width: 100%;box-sizing: border-box;height: 28px;margin: 0;'>", {
|
||||
title: _t('Add to favorites...'),
|
||||
buttons: [{
|
||||
text: _t('Cancel'),
|
||||
'class': 'btn btn-default',
|
||||
click: function (e) {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}, {
|
||||
text: _t('OK'),
|
||||
'class': 'btn btn-primary',
|
||||
click: function (e) {
|
||||
title = dialog.find("input").val();
|
||||
add({ title: title, link: link }, function () {
|
||||
dialog.dialog('close');
|
||||
});
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
setTimeout(function () {
|
||||
dialog.find("input").val(title).focus().select();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.manageFav = function () {
|
||||
NavService.openTabByName('menus.fav');
|
||||
};
|
||||
|
||||
function onUpdate(e, _ds) {
|
||||
if (ds !== _ds && ds._model === _ds._model) {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$on("ds:saved", onUpdate);
|
||||
$scope.$on("ds:removed", onUpdate);
|
||||
|
||||
update();
|
||||
}],
|
||||
template:
|
||||
"<ul class='dropdown-menu'>" +
|
||||
"<li><a href='' ng-click='addFav()' x-translate>Add to favorites...</a></li>" +
|
||||
"<li class='divider'></li>" +
|
||||
"<li ng-repeat='item in items track by item.name'><a ng-href='#{{item.link}}'>{{item.title}}</a></li>" +
|
||||
"<li class='divider'></li>" +
|
||||
"<li><a href='' ng-click='manageFav()' x-translate>Organize favorites...</a></li>" +
|
||||
"</ul>"
|
||||
};
|
||||
});
|
||||
|
||||
ui.directive('navMenuTasks', function() {
|
||||
return {
|
||||
replace: true,
|
||||
controller: ['$scope', '$location', 'TagService', 'NavService', function ($scope, $location, TagService, NavService) {
|
||||
|
||||
var TEAM_TASK = "com.axelor.team.db.TeamTask";
|
||||
|
||||
function taskText(count) {
|
||||
var n = count || 0;
|
||||
if (n <= 0) return _t('no tasks');
|
||||
return n > 1 ? _t('{0} tasks', n) : _t('{0} task', n);
|
||||
}
|
||||
|
||||
function update(data) {
|
||||
var counts = data || {};
|
||||
if (counts.current) {
|
||||
counts.css = 'badge-primary';
|
||||
}
|
||||
if (counts.pending) {
|
||||
counts.css = 'badge-important';
|
||||
}
|
||||
counts.currentText = taskText(counts.current);
|
||||
counts.pendingText = taskText(counts.pending);
|
||||
counts.total = Math.min(99, counts.current);
|
||||
|
||||
$scope.counts = counts;
|
||||
}
|
||||
|
||||
TagService.listen(function (data) {
|
||||
update(data.tasks || {});
|
||||
});
|
||||
|
||||
$scope.showTasks = function (type) {
|
||||
NavService.openTabByName('team.tasks.' + type);
|
||||
};
|
||||
|
||||
function onDataChange(e, ds) {
|
||||
if (ds._model === TEAM_TASK) {
|
||||
TagService.find();
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$on('ds:saved', onDataChange);
|
||||
$scope.$on('ds:removed', onDataChange);
|
||||
|
||||
update({});
|
||||
}],
|
||||
template:
|
||||
"<li class='dropdown'>" +
|
||||
"<a href='' class='nav-link-tasks dropdown-toggle' data-toggle='dropdown'>" +
|
||||
"<i class='fa fa-bell'></i>" +
|
||||
"<span class='badge' ng-show='counts.css' ng-class='counts.css'>{{counts.total}}</span>" +
|
||||
"</a>" +
|
||||
"<ul class='dropdown-menu'>" +
|
||||
"<li>" +
|
||||
"<a href='' ng-click='showTasks(\"due\")'>" +
|
||||
"<span class='nav-link-user-name' x-translate>Tasks due</span>" +
|
||||
"<span class='nav-link-user-sub' ng-class='{\"fg-red\": counts.pending > 0}'>{{counts.pendingText}}</span>" +
|
||||
"</a>" +
|
||||
"</li>" +
|
||||
"<li class='divider'></li>" +
|
||||
"<li>" +
|
||||
"<a href='' ng-click='showTasks((\"todo\"))'>" +
|
||||
"<span class='nav-link-user-name' x-translate>Tasks todo</span>" +
|
||||
"<span class='nav-link-user-sub'>{{counts.currentText}}</span>" +
|
||||
"</a>" +
|
||||
"</li>" +
|
||||
"</ul>" +
|
||||
"</li>"
|
||||
};
|
||||
});
|
||||
|
||||
})();
|
||||
85
sophal/js/widget/widget.navtabs.js
Normal file
85
sophal/js/widget/widget.navtabs.js
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
(function() {
|
||||
|
||||
"use strict";
|
||||
|
||||
var ui = angular.module('axelor.ui');
|
||||
|
||||
ui.directive('uiNavTabs', function() {
|
||||
return {
|
||||
restrict: 'EA',
|
||||
replace: true,
|
||||
link: function(scope, elem, attrs) {
|
||||
|
||||
if (scope.singleTabOnly) {
|
||||
return elem.parent().addClass("view-tabs-single");
|
||||
}
|
||||
|
||||
scope.$watch('tabs.length', function navTabsWatch(value, oldValue){
|
||||
if (value != oldValue) elem.trigger('adjust:tabs');
|
||||
});
|
||||
|
||||
var menu = $();
|
||||
|
||||
setTimeout(function () {
|
||||
|
||||
elem.bsTabs();
|
||||
elem.on('contextmenu', '.nav-tabs-main > li > a', showMenu);
|
||||
|
||||
menu = elem.find('#nav-tabs-menu');
|
||||
menu.css({
|
||||
position: 'absolute',
|
||||
zIndex: 1000
|
||||
}).hide();
|
||||
});
|
||||
|
||||
function showMenu(e) {
|
||||
var tabElem = $(e.target).parents('li:first');
|
||||
var tabScope = tabElem.data('$scope');
|
||||
if (!tabScope || !tabScope.tab) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
scope.current = tabScope.tab;
|
||||
scope.$timeout(function () {
|
||||
var offset = elem.offset();
|
||||
menu.show().css({
|
||||
left: e.pageX - offset.left,
|
||||
top: e.pageY - offset.top
|
||||
});
|
||||
$(document).on('click.nav-tabs-menu', hideMenu);
|
||||
});
|
||||
}
|
||||
|
||||
function hideMenu(e) {
|
||||
scope.$timeout(function () {
|
||||
scope.current = null;
|
||||
menu.hide();
|
||||
});
|
||||
$(document).off('click.nav-tabs-menu');
|
||||
}
|
||||
},
|
||||
templateUrl: 'partials/nav-tabs.html'
|
||||
};
|
||||
});
|
||||
|
||||
})();
|
||||
436
sophal/js/widget/widget.navtree.js
Normal file
436
sophal/js/widget/widget.navtree.js
Normal file
@ -0,0 +1,436 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
(function() {
|
||||
|
||||
"use strict";
|
||||
|
||||
var ui = angular.module('axelor.ui');
|
||||
|
||||
ui.directive('uiNavTree', ['MenuService', 'TagService', function(MenuService, TagService) {
|
||||
|
||||
return {
|
||||
scope: {
|
||||
itemClick: "&"
|
||||
},
|
||||
controller: ["$scope", "$q", function ($scope, $q) {
|
||||
|
||||
var items = [];
|
||||
var menus = [];
|
||||
var nodes = {};
|
||||
var searchItems = [];
|
||||
var deferred = $q.defer();
|
||||
|
||||
var handler = $scope.itemClick();
|
||||
|
||||
function canAccept(item) {
|
||||
return item.left || item.left === undefined;
|
||||
}
|
||||
|
||||
this.onClick = function (e, menu) {
|
||||
if (menu.action && (menu.children||[]).length === 0) {
|
||||
handler(e, menu);
|
||||
}
|
||||
};
|
||||
|
||||
this.load = function (data) {
|
||||
if (!data || !data.length) return;
|
||||
|
||||
items = data;
|
||||
items.forEach(function (item) {
|
||||
nodes[item.name] = item;
|
||||
item.children = [];
|
||||
});
|
||||
|
||||
items.forEach(function (item) {
|
||||
var node = nodes[item.parent];
|
||||
if (node) {
|
||||
node.children.push(item);
|
||||
} else if (canAccept(item)){
|
||||
menus.push(item);
|
||||
item.icon = item.icon || 'fa-bars';
|
||||
item.iconBackground = item.iconBackground || 'green';
|
||||
}
|
||||
});
|
||||
|
||||
var markForSidebar = function (item) {
|
||||
item.sidebar = true;
|
||||
item.children.forEach(markForSidebar);
|
||||
};
|
||||
menus.forEach(markForSidebar);
|
||||
|
||||
items.forEach(function (item) {
|
||||
if (item.children.length === 0) {
|
||||
delete item.children;
|
||||
var label = item.title;
|
||||
var parent = nodes[item.parent];
|
||||
var lastParent;
|
||||
while (parent) {
|
||||
lastParent = parent;
|
||||
parent = nodes[parent.parent];
|
||||
if (parent) {
|
||||
label = lastParent.title + "/" + label;
|
||||
}
|
||||
}
|
||||
searchItems.push(_.extend({
|
||||
title: item.title,
|
||||
label: label,
|
||||
action: item.action,
|
||||
category: lastParent ? lastParent.name : '',
|
||||
categoryTitle: lastParent ? lastParent.title : ''
|
||||
}));
|
||||
}
|
||||
});
|
||||
$scope.menus = menus;
|
||||
$scope.searchItems = searchItems;
|
||||
deferred.resolve();
|
||||
};
|
||||
|
||||
this.update = function (data) {
|
||||
if (!data || data.length === 0) return;
|
||||
data.forEach(function (item) {
|
||||
var node = nodes[item.name];
|
||||
if (node) {
|
||||
node.tag = item.tag;
|
||||
node.tagStyle = item.tagStyle;
|
||||
if (node.tagStyle) {
|
||||
node.tagCss = "label-" + node.tagStyle;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var that = this;
|
||||
TagService.listen(function (data) {
|
||||
that.update(data.tags);
|
||||
});
|
||||
|
||||
function findProp(node, name) {
|
||||
if (node[name]) {
|
||||
return node[name];
|
||||
}
|
||||
var parent = nodes[node.parent];
|
||||
if (parent) {
|
||||
return findProp(parent, name);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function updateTabStyle(tab) {
|
||||
if (tab.icon || tab.fa) {
|
||||
return;
|
||||
}
|
||||
var node = _.findWhere(nodes, { action: tab.action, sidebar: true });
|
||||
if (node) {
|
||||
tab.icon = tab.icon || findProp(node, 'icon');
|
||||
tab.color = tab.color || findProp(node, 'iconBackground');
|
||||
if (tab.icon && tab.icon.indexOf('fa') === 0) {
|
||||
tab.fa = tab.icon;
|
||||
delete tab.icon;
|
||||
} else {
|
||||
tab.fa = tab.fa || findProp(node, 'fa');
|
||||
}
|
||||
if (tab.icon) {
|
||||
tab.fa = null;
|
||||
}
|
||||
if (tab.color && tab.color.indexOf('#') != 0) {
|
||||
tab.topCss = 'bg-' + tab.color;
|
||||
tab.fa = tab.fa ? tab.fa + ' fg-' + tab.color : null;
|
||||
tab.color = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MenuService.updateTabStyle = function (tab) {
|
||||
deferred.promise.then(function () {
|
||||
updateTabStyle(tab);
|
||||
});
|
||||
};
|
||||
}],
|
||||
link: function (scope, element, attrs, ctrl) {
|
||||
var input = element.find('input');
|
||||
scope.showSearch = !!axelor.device.mobile;
|
||||
scope.toggleSearch = function (show) {
|
||||
input.val('');
|
||||
if (!axelor.device.mobile) {
|
||||
scope.showSearch = show === undefined ? !scope.showSearch : show;
|
||||
}
|
||||
};
|
||||
scope.onShowSearch = function () {
|
||||
scope.showSearch = true;
|
||||
setTimeout(function () {
|
||||
input.val('').focus();
|
||||
});
|
||||
};
|
||||
|
||||
input.attr('placeholder', _t('Search...'));
|
||||
input.blur(function (e) {
|
||||
scope.$timeout(function () {
|
||||
scope.toggleSearch(false);
|
||||
});
|
||||
});
|
||||
input.keydown(function (e) {
|
||||
if (e.keyCode === 27) { // escape
|
||||
scope.$timeout(function () {
|
||||
scope.toggleSearch(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function search(request, response) {
|
||||
var term = request.term;
|
||||
var items = _.filter(scope.searchItems, function (item) {
|
||||
var text = item.categoryTitle + '/' + item.label;
|
||||
var search = term;
|
||||
if (search[0] === '/') {
|
||||
search = search.substring(1);
|
||||
text = item.title;
|
||||
}
|
||||
text = text.replace('/', '').toLowerCase();
|
||||
if (search[0] === '"' || search[0] === '=') {
|
||||
search = search.substring(1);
|
||||
if (search.indexOf('"') === search.length - 1) {
|
||||
search = search.substring(0, search.length - 1);
|
||||
}
|
||||
return text.indexOf(search) > -1;
|
||||
}
|
||||
var parts = search.toLowerCase().split(/\s+/);
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
if (text.indexOf(parts[i]) === -1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return parts.length > 0;
|
||||
});
|
||||
response(items);
|
||||
}
|
||||
|
||||
MenuService.all().success(function (res) {
|
||||
ctrl.load(res.data);
|
||||
input.autocomplete({
|
||||
source: search,
|
||||
select: function (e, ui) {
|
||||
ctrl.onClick(e, ui.item);
|
||||
scope.$timeout(function () {
|
||||
scope.toggleSearch(false);
|
||||
});
|
||||
},
|
||||
appendTo: element.parent(),
|
||||
open: function () {
|
||||
element.children('.nav-tree').hide();
|
||||
},
|
||||
close: function (e) {
|
||||
element.children('.nav-tree').show();
|
||||
}
|
||||
});
|
||||
|
||||
input.data('autocomplete')._renderMenu = function (ul, items) {
|
||||
var all = _.groupBy(items, 'category');
|
||||
var that = this;
|
||||
scope.menus.forEach(function (menu) {
|
||||
var found = all[menu.name];
|
||||
if (found) {
|
||||
ul.append($("<li class='ui-menu-category'>").html(menu.title));
|
||||
found.forEach(function (item) {
|
||||
that._renderItemData(ul, item);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
},
|
||||
replace: true,
|
||||
template:
|
||||
"<div>" +
|
||||
"<div class='nav-search-toggle' ng-show='!showSearch'>" +
|
||||
"<i ng-click='onShowSearch()' class='fa fa-angle-down'></i>" +
|
||||
"</div>" +
|
||||
"<div class='nav-search' ng-show='showSearch'>" +
|
||||
"<input type='text'>" +
|
||||
"</div>" +
|
||||
"<ul class='nav nav-tree'>" +
|
||||
"<li ng-repeat='menu in menus track by menu.name' ui-nav-sub-tree x-menu='menu'></li>" +
|
||||
"</ul>" +
|
||||
"</div>"
|
||||
};
|
||||
}]);
|
||||
|
||||
ui.directive('uiNavSubTree', ['$compile', function ($compile) {
|
||||
|
||||
return {
|
||||
scope: {
|
||||
menu: "="
|
||||
},
|
||||
require: "^uiNavTree",
|
||||
link: function (scope, element, attrs, ctrl) {
|
||||
var menu = scope.menu;
|
||||
if (menu.icon && menu.icon.indexOf('fa') === 0) {
|
||||
menu.fa = menu.icon;
|
||||
delete menu.icon;
|
||||
}
|
||||
if (menu.tagStyle) {
|
||||
menu.tagCss = "label-" + menu.tagStyle;
|
||||
}
|
||||
if (menu.children) {
|
||||
var sub = $(
|
||||
"<ul class='nav ui-nav-sub-tree'>" +
|
||||
"<li ng-repeat='child in menu.children track by child.name' ui-nav-sub-tree x-menu='child'></li>" +
|
||||
"</ul>");
|
||||
sub = $compile(sub)(scope);
|
||||
sub.appendTo(element);
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
var icon = element.find("span.nav-icon:first");
|
||||
if (menu.iconBackground && icon.length > 0) {
|
||||
var cssName = menu.parent ? 'color' : 'background-color';
|
||||
var clsName = menu.parent ? 'fg-' : 'bg-';
|
||||
|
||||
if (!menu.parent) {
|
||||
icon.addClass("fg-white");
|
||||
}
|
||||
|
||||
if (menu.iconBackground.indexOf("#") === 0) {
|
||||
icon.css(cssName, menu.iconBackground);
|
||||
} else {
|
||||
icon.addClass(clsName + menu.iconBackground);
|
||||
}
|
||||
|
||||
// get computed color value
|
||||
var color = icon.css(cssName);
|
||||
var bright = d3.rgb(color).brighter(.3).toString();
|
||||
|
||||
// add hover effect
|
||||
element.hover(function () {
|
||||
icon.css(cssName, bright);
|
||||
}, function () {
|
||||
icon.css(cssName, color);
|
||||
});
|
||||
|
||||
// use same color for vertical line
|
||||
if (!menu.parent) {
|
||||
element.css("border-left-color", color);
|
||||
element.hover(function () {
|
||||
element.css("border-left-color", color);
|
||||
}, function () {
|
||||
element.css("border-left-color", bright);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var animation = false;
|
||||
|
||||
function show(el) {
|
||||
|
||||
var parent = el.parent("li");
|
||||
|
||||
if (animation || parent.hasClass('open')) {
|
||||
return;
|
||||
}
|
||||
|
||||
function done() {
|
||||
parent.addClass('open');
|
||||
parent.removeClass('animate');
|
||||
el.height('');
|
||||
animation = false;
|
||||
}
|
||||
|
||||
hide(parent.siblings("li.open").children('ul'));
|
||||
|
||||
animation = true;
|
||||
|
||||
parent.addClass('animate');
|
||||
el.height(el[0].scrollHeight);
|
||||
|
||||
setTimeout(done, 300);
|
||||
}
|
||||
|
||||
function hide(el) {
|
||||
|
||||
var parent = el.parent("li");
|
||||
|
||||
if (animation || !parent.hasClass('open')) {
|
||||
return;
|
||||
}
|
||||
|
||||
function done() {
|
||||
parent.removeClass('open');
|
||||
parent.removeClass('animate');
|
||||
animation = false;
|
||||
}
|
||||
|
||||
animation = true;
|
||||
|
||||
el.height(el.height())[0].offsetHeight;
|
||||
parent.addClass('animate');
|
||||
el.height(0);
|
||||
|
||||
setTimeout(done, 300);
|
||||
}
|
||||
|
||||
element.on('click', '> a', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (animation) return;
|
||||
|
||||
var $list = element.children('ul');
|
||||
|
||||
element.parents('.nav-tree').find('li.active').not(element).removeClass('active');
|
||||
element.addClass('active');
|
||||
|
||||
if (menu.action && (menu.children||[]).length === 0) {
|
||||
scope.$applyAsync(function () {
|
||||
ctrl.onClick(e, menu);
|
||||
});
|
||||
}
|
||||
if ($list.length === 0) return;
|
||||
if (element.hasClass('open')) {
|
||||
hide($list);
|
||||
} else {
|
||||
show($list);
|
||||
}
|
||||
});
|
||||
|
||||
if (menu.help) {
|
||||
setTimeout(function () {
|
||||
var tooltip = element.children('a')
|
||||
.addClass('has-help')
|
||||
.tooltip({
|
||||
html: true,
|
||||
title: menu.help,
|
||||
placement: 'right',
|
||||
delay: { show: 500, hide: 100 },
|
||||
container: 'body'
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
replace: true,
|
||||
template:
|
||||
"<li ng-class='{folder: menu.children, tagged: menu.tag }' data-name='{{::menu.name}}'>" +
|
||||
"<a href='#'>" +
|
||||
"<img class='nav-image' ng-if='::menu.icon' ng-src='{{::menu.icon}}'></img>" +
|
||||
"<span class='nav-icon' ng-if='::menu.fa'><i class='fa' ng-class='::menu.fa'></i></span>" +
|
||||
"<span ng-show='menu.tag' ng-class='menu.tagCss' class='nav-tag label'>{{menu.tag}}</span>" +
|
||||
"<span class='nav-title'>{{::menu.title}}</span>" +
|
||||
"</a>" +
|
||||
"</li>"
|
||||
};
|
||||
}]);
|
||||
|
||||
})();
|
||||
1573
sophal/js/widget/widget.search.js
Normal file
1573
sophal/js/widget/widget.search.js
Normal file
File diff suppressed because it is too large
Load Diff
2913
sophal/js/widget/widget.slickgrid.js
Normal file
2913
sophal/js/widget/widget.slickgrid.js
Normal file
File diff suppressed because it is too large
Load Diff
2893
sophal/js/widget/widget.slickgrid_old.js
Normal file
2893
sophal/js/widget/widget.slickgrid_old.js
Normal file
File diff suppressed because it is too large
Load Diff
473
sophal/js/widget/widget.update.js
Normal file
473
sophal/js/widget/widget.update.js
Normal file
@ -0,0 +1,473 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
(function() {
|
||||
|
||||
'use strict';
|
||||
|
||||
var ui = angular.module('axelor.ui');
|
||||
|
||||
|
||||
ui.directive('uiDeleteButton', [function () {
|
||||
return {
|
||||
link: function (scope, element, attrs) {
|
||||
|
||||
},
|
||||
replace: true,
|
||||
template:
|
||||
"<div class='btn-group delete-button'>" +
|
||||
"<button class='btn' ng-click='onDelete()' ng-if='hasButton(\"delete\")' ng-disabled='!canDelete()' title='{{ \"Delete\" | t}}'>" +
|
||||
"<i class='fa fa-trash-o'></i> <span ng-if='::!tbTitleHide' x-translate>Delete</span>" +
|
||||
"</button>" +
|
||||
"<button class='btn dropdown-toggle' data-toggle='dropdown' ng-if='hasButton(\"archive\")' ng-disabled='!canArchive()'>" +
|
||||
"<i class='fa fa-caret-down'></i>" +
|
||||
"</button>" +
|
||||
"<ul class='dropdown-menu' ng-if='hasButton(\"archive\")'>" +
|
||||
"<li><a href='' ng-click='onArchive()' x-translate>Archive</a></li>" +
|
||||
"<li><a href='' ng-click='onUnarchive()' x-translate>Unarchive</a></li>" +
|
||||
"</ul>" +
|
||||
"</div>"
|
||||
};
|
||||
}]);
|
||||
|
||||
ui.directive('uiUpdateButton', ['$compile', function ($compile) {
|
||||
|
||||
return {
|
||||
scope: {
|
||||
handler: '='
|
||||
},
|
||||
link: function (scope, element, attrs) {
|
||||
|
||||
var menu = element.find('.update-menu'),
|
||||
toggleButton = null;
|
||||
|
||||
scope.visible = false;
|
||||
|
||||
scope.onMassUpdate = function (e) {
|
||||
if (menu && menu.is(':visible')) {
|
||||
hideMenu();
|
||||
return;
|
||||
}
|
||||
toggleButton = $(e.currentTarget);
|
||||
toggleButton.addClass("active");
|
||||
|
||||
scope.onShow(e, menu);
|
||||
|
||||
$(document).on('mousedown.update-menu', onMouseDown);
|
||||
|
||||
scope.$applyAsync(function () {
|
||||
scope.visible = true;
|
||||
});
|
||||
};
|
||||
|
||||
scope.onCancel = function () {
|
||||
hideMenu();
|
||||
};
|
||||
|
||||
scope.canMassUpdate = function () {
|
||||
return true;
|
||||
};
|
||||
|
||||
if (scope.handler && scope.handler.canMassUpdate) {
|
||||
scope.canMassUpdate = scope.handler.canMassUpdate;
|
||||
}
|
||||
|
||||
function hideMenu() {
|
||||
$(document).off('mousedown.update-menu', onMouseDown);
|
||||
if (toggleButton) {
|
||||
toggleButton.removeClass("active");
|
||||
}
|
||||
scope.$applyAsync(function () {
|
||||
scope.visible = false;
|
||||
});
|
||||
return menu.hide();
|
||||
}
|
||||
|
||||
function onMouseDown(e) {
|
||||
var all = $(menu).add(toggleButton);
|
||||
if (all.is(e.target) || all.has(e.target).length > 0) {
|
||||
return;
|
||||
}
|
||||
all = $('.ui-widget-overlay,.ui-datepicker:visible,.ui-dialog:visible,.ui-menu:visible');
|
||||
if (all.is(e.target) || all.has(e.target).length > 0) {
|
||||
return;
|
||||
}
|
||||
if(menu){
|
||||
hideMenu();
|
||||
}
|
||||
}
|
||||
|
||||
// append box after the button
|
||||
scope.$timeout(function () {
|
||||
element.parents('.view-container').after(menu);
|
||||
});
|
||||
|
||||
scope.$on('$destroy', function() {
|
||||
$(document).off('mousedown.update-menu', onMouseDown);
|
||||
if (menu) {
|
||||
menu.remove();
|
||||
menu = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
replace: true,
|
||||
template:
|
||||
"<button class='btn update-menu-button' ng-click='onMassUpdate($event)' ng-disabled='!canMassUpdate()' >" +
|
||||
"<i class='fa fa-caret-down'></i>" +
|
||||
"<div ui-update-menu x-handler='handler' x-visible='visible'></div>" +
|
||||
"</button>"
|
||||
};
|
||||
}]);
|
||||
|
||||
ui.directive('uiUpdateDummy', function () {
|
||||
|
||||
return {
|
||||
require: '^uiUpdateForm',
|
||||
scope: {
|
||||
record: '='
|
||||
},
|
||||
controller: ['$scope', '$element', 'DataSource', 'ViewService', function($scope, $element, DataSource, ViewService) {
|
||||
|
||||
var parent = $scope.$parent;
|
||||
var handler = parent.handler;
|
||||
|
||||
$scope._viewParams = {
|
||||
model: handler._model,
|
||||
views: []
|
||||
};
|
||||
|
||||
ui.ViewCtrl($scope, DataSource, ViewService);
|
||||
ui.FormViewCtrl.call(this, $scope, $element);
|
||||
|
||||
function prepare(fields) {
|
||||
|
||||
var schema = {
|
||||
cols: 1,
|
||||
type: 'form',
|
||||
items: _.values(fields)
|
||||
};
|
||||
|
||||
$scope.fields = fields;
|
||||
$scope.schema = schema;
|
||||
$scope.schema.loaded = true;
|
||||
}
|
||||
|
||||
var initialized = false;
|
||||
$scope.show = function () {
|
||||
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
|
||||
var unwatch = parent.$watch('fields', function massFieldsWatch(fields) {
|
||||
if (_.isEmpty(fields)) return;
|
||||
unwatch();
|
||||
prepare(fields);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.setEditable();
|
||||
$scope.show();
|
||||
}],
|
||||
link: function (scope, element, attrs) {
|
||||
element.hide();
|
||||
},
|
||||
template: "<div class='hide' ui-view-form x-handler='true'></div>"
|
||||
};
|
||||
});
|
||||
|
||||
ui.directive('uiUpdateForm', function () {
|
||||
|
||||
function findFields(fields, items) {
|
||||
|
||||
var all = {};
|
||||
var accept = function (field) {
|
||||
var name = field.name;
|
||||
if (!field.massUpdate) return;
|
||||
if (/^(id|version|selected|archived|((updated|created)(On|By)))$/.test(name)) return;
|
||||
if (field.large || field.unique) return;
|
||||
switch (field.type) {
|
||||
case 'one-to-many':
|
||||
case 'many-to-many':
|
||||
case 'binary':
|
||||
return;
|
||||
}
|
||||
|
||||
if (field.target) {
|
||||
field.canNew = false;
|
||||
field.canEdit = false;
|
||||
}
|
||||
field.hidden = false;
|
||||
field.required = false;
|
||||
field.readonly = false;
|
||||
field.onChange = null;
|
||||
field.placeholder = field.placeholder || field.title;
|
||||
|
||||
all[name] = field;
|
||||
};
|
||||
|
||||
_.each(fields, function (field, name) { accept(field); });
|
||||
_.each(items, function (item) {
|
||||
var field = fields[item.name];
|
||||
if (field) {
|
||||
accept(_.extend({}, field, item, { type: field.type }));
|
||||
}
|
||||
});
|
||||
|
||||
return all;
|
||||
}
|
||||
|
||||
return {
|
||||
replace: true,
|
||||
controller: ['$scope', 'ViewService', function ($scope, ViewService) {
|
||||
|
||||
$scope.filters = [{}];
|
||||
$scope.options = [];
|
||||
|
||||
$scope.onInit = _.once(function (view) {
|
||||
|
||||
var handler = $scope.handler;
|
||||
var promise = ViewService.getFields(handler._model);
|
||||
promise.success(function (fields) {
|
||||
$scope.fields = findFields(fields, view.items);
|
||||
$scope.options = _.sortBy(_.values($scope.fields), 'title');
|
||||
$scope.record = {};
|
||||
});
|
||||
});
|
||||
|
||||
$scope.addFilter = function (filter) {
|
||||
var all = $scope.filters;
|
||||
var last = _.last(all);
|
||||
if (last && !last.field) return;
|
||||
if (all.length > 0 && all.length === $scope.options.length) return;
|
||||
$scope.filters.push(filter || {});
|
||||
$scope.updateSelection();
|
||||
};
|
||||
|
||||
$scope.removeFilter = function(filter) {
|
||||
var index = $scope.filters.indexOf(filter);
|
||||
if (index > -1) {
|
||||
$scope.filters.splice(index, 1);
|
||||
}
|
||||
if ($scope.filters.length === 0) {
|
||||
$scope.addFilter();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.notSelected = function (filter) {
|
||||
return function (opt) {
|
||||
return filter.field === opt.name || !opt.selected;
|
||||
};
|
||||
};
|
||||
|
||||
var values = null;
|
||||
var canUpdate = false;
|
||||
|
||||
function updateValues(record) {
|
||||
var keys = _.pluck($scope.filters, 'field'),
|
||||
vals = {};
|
||||
|
||||
_.each(keys, function (key) {
|
||||
if (key) {
|
||||
vals[key] = (record || {})[key];
|
||||
if (vals[key] === undefined) {
|
||||
vals[key] = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
values = vals;
|
||||
canUpdate = !_.isEmpty(values);
|
||||
}
|
||||
|
||||
$scope.updateSelection = function updateSelection () {
|
||||
|
||||
var selected = _.pluck($scope.filters, 'field');
|
||||
_.each($scope.options, function (opt) {
|
||||
opt.selected = selected.indexOf(opt.name) > -1;
|
||||
});
|
||||
|
||||
updateValues($scope.record);
|
||||
};
|
||||
|
||||
$scope.$watch('record', updateValues, true);
|
||||
|
||||
$scope.canUpdate = function () {
|
||||
return canUpdate;
|
||||
};
|
||||
|
||||
$scope.updateAll = false;
|
||||
|
||||
$scope.applyUpdate = function () {
|
||||
|
||||
var handler = $scope.handler;
|
||||
var ds = handler._dataSource;
|
||||
|
||||
function doUpdate() {
|
||||
var promise, items;
|
||||
|
||||
items = _.map(handler.selection, function(index) {
|
||||
return handler.dataView.getItem(index);
|
||||
});
|
||||
items = _.pluck(items, "id");
|
||||
|
||||
if ($scope.updateAll) {
|
||||
items = null;
|
||||
} else if (items.length === 0) {
|
||||
return $scope.onCancel();
|
||||
}
|
||||
|
||||
promise = ds.updateMass(values, items);
|
||||
promise.success(function () {
|
||||
handler.onRefresh();
|
||||
$scope.onCancel();
|
||||
});
|
||||
}
|
||||
|
||||
var count;
|
||||
if ($scope.updateAll) {
|
||||
count = ds._page.total;
|
||||
} else if(handler.selection && handler.selection.length > 0) {
|
||||
count = handler.selection.length;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
var message = _t('Do you really want to update all {0} record(s)?', count);
|
||||
axelor.dialogs.confirm(message, function (confirmed) {
|
||||
if (confirmed) {
|
||||
doUpdate();
|
||||
}
|
||||
});
|
||||
};
|
||||
}],
|
||||
link: function (scope, element, attrs) {
|
||||
|
||||
scope.onSelect = function (name) {
|
||||
scope.updateSelection();
|
||||
setTimeout(adjustEditors);
|
||||
};
|
||||
|
||||
scope.clearFilter = function() {
|
||||
scope.filters.length = 0;
|
||||
scope.addFilter();
|
||||
scope.record = {};
|
||||
adjustEditors();
|
||||
};
|
||||
|
||||
scope.remove = function(filter) {
|
||||
scope.removeFilter(filter);
|
||||
adjustEditors();
|
||||
};
|
||||
|
||||
scope.onCancel = function () {
|
||||
if (scope.$parent.onCancel) {
|
||||
scope.$parent.onCancel();
|
||||
}
|
||||
};
|
||||
|
||||
function adjustEditors() {
|
||||
|
||||
element.find('[x-place-for] [x-field]').each(function () {
|
||||
var editor = $(this);
|
||||
var parent = editor.data('$parent');
|
||||
editor.appendTo(parent);
|
||||
});
|
||||
|
||||
_.each(scope.filters, function (filter) {
|
||||
adjustEditor(filter.field);
|
||||
});
|
||||
}
|
||||
|
||||
function adjustEditor(name) {
|
||||
var span = element.find('[x-place-for=' + name + ']');
|
||||
var editor = element.find('[x-field=' + name + '].form-item-container,[x-field=' + name + '].boolean-item').first();
|
||||
var parent = editor.data('$parent');
|
||||
if (!parent) {
|
||||
parent = editor.parent();
|
||||
editor.data('$parent', parent);
|
||||
}
|
||||
editor.appendTo(span);
|
||||
}
|
||||
},
|
||||
template:
|
||||
"<form class='form-inline update-form filter-form'>" +
|
||||
"<strong x-translate>Mass Update</strong> " +
|
||||
"<hr>" +
|
||||
"<table class='form-layout'>" +
|
||||
"<tr ng-repeat='filter in filters' class='form-inline'>" +
|
||||
"<td class='filter-remove'>" +
|
||||
"<a href='' ng-click='remove(filter)'><i class='fa fa-times'></i></a>" +
|
||||
"</td>" +
|
||||
"<td class='form-item'>" +
|
||||
"<span class='form-item-container'>" +
|
||||
"<select ng-model='filter.field' ng-options='v.name as v.title for v in options | filter:notSelected(filter)' ng-change='onSelect(filter.field)'></select>" +
|
||||
"</span>" +
|
||||
"</td>" +
|
||||
"<td class='form-item' x-place-for='{{filter.field}}'>" +
|
||||
"</td>" +
|
||||
"</tr>" +
|
||||
"</table>" +
|
||||
"<div class='links'>"+
|
||||
"<a href='' ng-click='addFilter()' x-translate>Add Field</a>" +
|
||||
"<span class='divider'>|</span>"+
|
||||
"<a href='' ng-click='clearFilter()' x-translate>Clear</a>" +
|
||||
"</div>" +
|
||||
"<div ui-update-dummy x-record='record'></div>"+
|
||||
"</form>"
|
||||
};
|
||||
});
|
||||
|
||||
ui.directive('uiUpdateMenu', function () {
|
||||
|
||||
return {
|
||||
replace: true,
|
||||
scope: {
|
||||
handler: '='
|
||||
},
|
||||
link: function (scope, element, attrs) {
|
||||
|
||||
scope.$parent.onShow = function (event, menu) {
|
||||
scope.handler._viewPromise.then(function (view) {
|
||||
var elem = $(event.currentTarget);
|
||||
if (scope.onInit) {
|
||||
scope.onInit(view);
|
||||
}
|
||||
menu.show();
|
||||
menu.position({
|
||||
my: "left top",
|
||||
at: "left bottom",
|
||||
of: elem
|
||||
});
|
||||
});
|
||||
};
|
||||
},
|
||||
template:
|
||||
"<div class='update-menu filter-menu' ui-watch-if='$parent.visible'>" +
|
||||
"<div ui-update-form></div>" +
|
||||
"<hr>" +
|
||||
"<div class='form-inline'>" +
|
||||
"<button class='btn btn-small' ng-disabled='!canUpdate()' ng-click='applyUpdate()'><span x-translate>Update</span></button> " +
|
||||
"<button class='btn btn-small' ng-click='onCancel()'><span x-translate>Cancel</span></button> " +
|
||||
"<label class='checkbox update-all'>" +
|
||||
"<input type='checkbox' ng-model='updateAll'> <span x-translate>Update all</span>" +
|
||||
"</label> " +
|
||||
"</div>" +
|
||||
"</div>"
|
||||
};
|
||||
});
|
||||
|
||||
})();
|
||||
Reference in New Issue
Block a user