From 61c9f4f97546aff2de87c3073953d0f41a294b28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20G=C3=B3mez?= Date: Wed, 4 Dec 2013 01:57:33 +0000 Subject: [PATCH] Implemented callbacks for gui "fillers", that is, choices that calls server for information about how to fill other fields with returned values... --- .../org.eclipse.core.resources.prefs | 2 + server/src/uds/REST/methods/gui_callback.py | 75 +++++++++++ server/src/uds/REST/processors.py | 9 +- server/src/uds/static/adm/css/uds-admin.css | 10 +- server/src/uds/static/adm/js/api.js | 4 +- server/src/uds/static/adm/js/gui-form.js | 117 +++++++++++++++++- server/src/uds/static/adm/js/gui.js | 12 +- server/src/uds/templates/uds/admin/index.html | 2 +- .../templates/uds/admin/tmpl/fld/choice.html | 1 + server/src/uds/templates/uds/html5/index.html | 3 - 10 files changed, 209 insertions(+), 26 deletions(-) create mode 100644 server/src/uds/REST/methods/gui_callback.py diff --git a/server/.settings/org.eclipse.core.resources.prefs b/server/.settings/org.eclipse.core.resources.prefs index 7777ebc34..b6ffbc22e 100644 --- a/server/.settings/org.eclipse.core.resources.prefs +++ b/server/.settings/org.eclipse.core.resources.prefs @@ -11,6 +11,7 @@ encoding//src/server/urls.py=utf-8 encoding//src/uds/REST/__init__.py=utf-8 encoding//src/uds/REST/handlers.py=utf-8 encoding//src/uds/REST/methods/authenticators.py=utf-8 +encoding//src/uds/REST/methods/gui_callback.py=utf-8 encoding//src/uds/REST/methods/login_logout.py=utf-8 encoding//src/uds/REST/methods/networks.py=utf-8 encoding//src/uds/REST/methods/osmanagers.py=utf-8 @@ -151,6 +152,7 @@ encoding//src/uds/migrations/0012_auto__add_field_authenticator_small_name.py=ut encoding//src/uds/migrations/0013_auto__add_field_group_is_meta__add_field_uniqueid_stamp.py=utf-8 encoding//src/uds/migrations/0014_auto__add_field_network_net_string.py=utf-8 encoding//src/uds/migrations/0016_auto__add_field_userservice_cluster_node.py=utf-8 +encoding//src/uds/migrations/0017_change_tables.py=utf-8 encoding//src/uds/models.py=utf-8 encoding//src/uds/osmanagers/LinuxOsManager/LinuxOsManager.py=utf-8 encoding//src/uds/osmanagers/LinuxOsManager/__init__.py=utf-8 diff --git a/server/src/uds/REST/methods/gui_callback.py b/server/src/uds/REST/methods/gui_callback.py new file mode 100644 index 000000000..cf9ec257a --- /dev/null +++ b/server/src/uds/REST/methods/gui_callback.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2014 Virtual Cable S.L. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Virtual Cable S.L. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +''' +@author: Adolfo Gómez, dkmaster at dkmon dot com +''' +from __future__ import unicode_literals + +from uds.core.ui.UserInterface import gui +from uds.REST import Handler, RequestError, NotFound + +import logging + +logger = logging.getLogger(__name__) + +# Enclosed methods under /auth path + +class Callback(Handler): + path = 'gui' + authenticated = True # Public method + needs_staff = True # + + def get(self): + ''' + This login uses parameters to generate auth token + The alternative is to use the template tag inside "REST" that is called auth_token, that extracts an auth token from an user session + We can use any of this forms due to the fact that the auth token is in fact a session key + Parameters: + mandatory: + username: + password: + auth: + optional: + locale: (defaults to "en") + Result: + on success: { 'result': 'ok', 'auth': [auth_code] } + on error: { 'result: 'error', 'error': [error string] } + ''' + + logger.debug('Params: {0}'.format(self._params)) + + if len(self._args) != 1: + raise RequestError('Invalid Request') + + if gui.callbacks.has_key(self._args[0]): + return gui.callbacks[self._args[0]](self._params) + + raise NotFound('callback {0} not found'.format(self._args[0])) + \ No newline at end of file diff --git a/server/src/uds/REST/processors.py b/server/src/uds/REST/processors.py index 563a4b2ad..0c3da72b3 100644 --- a/server/src/uds/REST/processors.py +++ b/server/src/uds/REST/processors.py @@ -50,6 +50,13 @@ class ContentProcessor(object): def __init__(self, request): self._request = request + + def processGetParameters(self): + if self._request.method != 'GET': + return {} + + return self._request.GET.copy() + def processParameters(self): return '' @@ -70,7 +77,7 @@ class JsonProcessor(ContentProcessor): def processParameters(self): try: if len(self._request.body) == 0: - return {} + return self.processGetParameters() res = json.loads(self._request.body) logger.debug(res) return res diff --git a/server/src/uds/static/adm/css/uds-admin.css b/server/src/uds/static/adm/css/uds-admin.css index 7b9123293..c14fb22ee 100644 --- a/server/src/uds/static/adm/css/uds-admin.css +++ b/server/src/uds/static/adm/css/uds-admin.css @@ -66,7 +66,7 @@ table.dataTable tr.even td.sorting_3 { background-color: blue; }*/ /* modal dialogs & related*/ .modal-dialog { /* new custom width */ - width: 60%; + width: 90%; } @@ -76,6 +76,7 @@ table.dataTable tr.even td.sorting_3 { background-color: blue; }*/ .tooltip { z-index: 2014; } + /* Edit Below to Customize Widths > 768px */ @media (min-width:768px) { @@ -142,6 +143,11 @@ table.dataTable tr.even td.sorting_3 { background-color: blue; }*/ .label-tbl-button { display: inline-block; } + + .modal-dialog { + /* new custom width */ + width: 60%; + } + } -} diff --git a/server/src/uds/static/adm/js/api.js b/server/src/uds/static/adm/js/api.js index 52c6da3fe..f2e87a550 100644 --- a/server/src/uds/static/adm/js/api.js +++ b/server/src/uds/static/adm/js/api.js @@ -179,7 +179,7 @@ function BasicModelRest(path, options) { BasicModelRest.prototype = { // options: - // cacheKey: '.' --> do not cache + // cacheKey: '.' --> do not cache (undefined will set cacheKey to current path) // undefined -- > use path as key // success: success fnc to execute in case of success _requestPath: function(path, options) { @@ -322,7 +322,7 @@ BasicModelRest.prototype = { path = this.guiPath; } return this._requestPath(path, { - cacheKey: path, + cacheKey: '.', // Gui is not cacheable, it's dynamic and can change from call to call success: success_fnc, fail: fail_fnc, }); diff --git a/server/src/uds/static/adm/js/gui-form.js b/server/src/uds/static/adm/js/gui-form.js index 54bf11241..ad693d807 100644 --- a/server/src/uds/static/adm/js/gui-form.js +++ b/server/src/uds/static/adm/js/gui-form.js @@ -4,9 +4,24 @@ gui.forms = {}; + gui.forms.callback = function(formSelector, method, params, success_fnc) { + var path = 'gui/callback/' + method; + var p = []; + $.each(params, function(index, val) { + p.push(val.name + '=' + encodeURIComponent(val.value)); + }); + path = path + '?' + p.join('&'); + api.getJson(path, { + success: success_fnc, + }); + + }; + // Returns form fields that will manage a gui description (new or edit) gui.forms.fieldsToHtml = function(itemGui, item, editing) { var html = ''; + var fillers = []; // Fillers (callbacks) + var originalValues = {}; // Initial stored values (defaults to "reset" form and also used on fillers callback to try to restore previous value) // itemGui is expected to have fields sorted by .gui.order (REST api returns them sorted) $.each(itemGui, function(index, f){ gui.doLog(f); @@ -15,8 +30,19 @@ if( f.gui.type == 'text' && f.gui.multiline ) { f.gui.type = 'textbox'; } + var value = item[f.name] || f.gui.value || f.gui.defvalue; + // We need to convert "array" values for multichoices to single list of ids (much more usable right here) + if( f.gui.type == 'multichoice') { + var newValue = []; + $.each(value, function(undefined, val) { + newValue.push(val.id); + }); + value = newValue; + } + + originalValues[f.name] = value; // Store original value html += api.templates.evaluate('tmpl_fld_'+f.gui.type, { - value: item[f.name] || f.gui.value || f.gui.defvalue, // If no value present, use default value + value: value, // If no value present, use default value values: f.gui.values, label: f.gui.label, length: f.gui.length, @@ -28,31 +54,106 @@ name: f.name, css: 'modal_field_data', }); + + // if this field has a filler (callback to get data) + if( f.gui.fills ) { + gui.doLog('This field has a filler'); + fillers.push({ name: f.name, callbackName: f.gui.fills.callbackName, parameters: f.gui.fills.parameters }); + } + }); - return html; + return { html: html, fillers: fillers, originalValues: originalValues }; }; gui.forms.fromFields = function(fields, item) { var editing = item !== undefined; // Locate real Editing item = item || {id:''}; + var form = '
' + ''; + var fillers = []; + var originalValues = {}; + if( fields.tabs ) { var id = 'tab-' + Math.random().toString().split('.')[1]; // Get a random base ID for tab entries var tabs = []; var tabsContent = []; var active = ' active in' ; $.each(fields.tabs, function(index, tab){ - tabsContent.push('
' + gui.forms.fieldsToHtml(tab.fields, item) + '
' ); + var h = gui.forms.fieldsToHtml(tab.fields, item); + tabsContent.push('
' + h.html + '
' ); tabs.push('
  • ' + tab.title + '
  • ' ); active = ''; + fillers = fillers.concat(h.fillers); // Fillers (callback based) + $.extend(originalValues, h.originalValues); // Original values + gui.doLog('Fillers:', h.fillers); }); form += '
    ' + tabsContent.join('\n') + '
    '; } else { - form += gui.forms.fieldsToHtml(fields, item, editing); + var h = gui.forms.fieldsToHtml(fields, item, editing); + form += h.html; + fillers = fillers.concat(h.fillers); + $.extend(originalValues, h.originalValues); } form += '
    '; - return form; + + gui.doLog('Original values: ', originalValues); + + // Init function for callbacks. + // Callbacks can only be attached to "Selects", but it's parameters can be got from any field + // This needs the "form selector" as base for setting callbacks, etc.. + var init = function(formSelector) { + gui.doLog(formSelector, fillers); + + /*var pos = 0; + var triggerChangeSequentially = function() { + if( pos >= fillers.length ) + return; + $(formSelector + ' [name="' + fillers[pos].name + '"]').trigger('change'); + pos = pos + 1; + };*/ + + var onChange = function(filler) { + return function() { + gui.doLog('Onchange invoked for ', filler); + // Attach on change method to each filler, and after that, all + var params = []; + $.each(filler.parameters, function(undefined, p){ + var val = $(formSelector + ' [name="' + p + '"]').val(); + params.push({name: p, value: val}); + }); + gui.forms.callback(formSelector, filler.callbackName, params, function(data){ + $.each(data, function(undefined, sel){ + // Update select contents with returned values + var $select = $(formSelector + ' [name="' + sel.name + '"]'); + + $select.empty(); + $.each(sel.values, function(undefined, value){ + $select.append(''); + }); + $select.val(originalValues[sel.name]); + // Refresh selectpicker updated + if($select.hasClass('selectpicker')) + $select.selectpicker('refresh'); + // Trigger change for the changed item + $select.trigger('change'); + + }); + //triggerChangeSequentially(); + }); + }; + }; + + // Sets the "on change" event for select with fillers (callbacks that fills other fields) + $.each(fillers, function(undefined, f) { + $(formSelector + ' [name="' + f.name + '"]').on('change', onChange(f)); + }); + + if( fillers.length ) + $(formSelector + ' [name="' + fillers[0].name + '"]').trigger('change'); + }; + + return { 'html': form, 'init': init }; // Returns the form and a initialization function for the form, that must be invoked to start it }; // Reads fields from a form @@ -74,9 +175,13 @@ gui.forms.launchModal = function(title, fields, item, onSuccess) { var id = 'modal-' + Math.random().toString().split('.')[1]; // Get a random ID for this modal - gui.appendToWorkspace(gui.modal(id, title, gui.forms.fromFields(fields, item))); + var ff = gui.forms.fromFields(fields, item); + gui.appendToWorkspace(gui.modal(id, title, ff.html)); id = '#' + id; // for jQuery + if( ff.init ) + ff.init(id); + // Get form var $form = $(id + ' form'); diff --git a/server/src/uds/static/adm/js/gui.js b/server/src/uds/static/adm/js/gui.js index 786687394..d2f175f9c 100644 --- a/server/src/uds/static/adm/js/gui.js +++ b/server/src/uds/static/adm/js/gui.js @@ -128,22 +128,12 @@ }); }; - gui.failRequestMessageFnc = function(jqXHR, textStatus, errorThrown) { - api.templates.get('request_failed', function(tmpl) { - gui.clearWorkspace(); - gui.appendToWorkspace(api.templates.evaluate(tmpl, { - error: jqXHR.responseText, - })); - }); - gui.setLinksEvents(); - }; - gui.failRequestModalFnc = function(title) { return function(jqXHR, textStatus, errorThrown) { // fail on put gui.launchModal(title, jqXHR.responseText, ' '); }; }; - + gui.clearWorkspace = function() { $('#content').empty(); $('#minimized').empty(); diff --git a/server/src/uds/templates/uds/admin/index.html b/server/src/uds/templates/uds/admin/index.html index f61b98ce8..3694e7b29 100644 --- a/server/src/uds/templates/uds/admin/index.html +++ b/server/src/uds/templates/uds/admin/index.html @@ -110,7 +110,7 @@ // Initialize gui gui.init(); // set default error function - api.defaultFail = gui.failRequestMessageFnc; + api.defaultFail = gui.failRequestModalFnc(gettext('Error on request')); }); {% block js %}{% endblock %} diff --git a/server/src/uds/templates/uds/admin/tmpl/fld/choice.html b/server/src/uds/templates/uds/admin/tmpl/fld/choice.html index 29358de0d..d7b1eb48c 100644 --- a/server/src/uds/templates/uds/admin/tmpl/fld/choice.html +++ b/server/src/uds/templates/uds/admin/tmpl/fld/choice.html @@ -1,5 +1,6 @@ {% extends "uds/admin/tmpl/fld/form-group.html" %} {% load i18n %} +{% comment %}The choice item MUST be a Select{% endcomment %} {% block field %} {% verbatim %}