diff --git a/js/dist/humhub.all.js b/js/dist/humhub.all.js deleted file mode 100644 index 24de17f482..0000000000 --- a/js/dist/humhub.all.js +++ /dev/null @@ -1,1862 +0,0 @@ -/** - * Sets up the humhub namespace and module management. - * This namespace provides the following functions: - * - * initModule - for adding modules to this namespace and initializing them - * - * @type @exp;humhub|@call;humhub.core_L4|Function - */ -var humhub = humhub || (function($) { - /** - * Contains all modules by namespace e.g. modules.ui.modal - * @type object - */ - var modules = {}; - - /** - * Used to collect modules added while initial page load. - * These modules will be intitialized after the document is ready. - * @type Array - */ - var initialModules = []; - - /** - * Is set wehen document is ready - * @type Boolean - */ - var initialized = false; - - /** - * Adds an module to the namespace. And initializes either after dom is ready. - * The id can be provided either as - * - * - full namespace humhub.modules.ui.modal - * - or module.ui.modal - * - or short ui.modal - * - * Usage: - * - * humhub.initModule('ui.modal', function(module, require, $) {...}); - * - * This would create an empty ui namespace (if not already created before) and - * initializes the given module. - * - * The module can export functions ans properties by - * using either - * - * module.myFunction = function() {...} - * ... - * - * or - * - * module.export({ - * myFunction: function() {...}, - * ... - * }); - * - * The export function can be called as often as needed (but should be called - * once at the end of the module). - * A module can provide an init function, which is called automatically - * after the document is ready. - * - * Dependencies: - * - * The core modules are initialized in a specific order to provide the needed - * dependencies for each module. The order is given by the order of initModule calls - * and in case of core modules configured in the build script. - * - * A module can be received by using the required function within a module bock. - * You can either depend on a module at initialisation time or within your functions. - * - * Usage: - * - * var modal = require('ui.modal); - * - * @param {type} id the namespaced id - * @param {type} module - * @returns {undefined} - */ - var initModule = function(id, module) { - //Create module in the namespace and add helper functions - var instance = resolveNameSpace(id, true); - instance.id = 'humhub.modules.'+_cutModulePrefix(id); - instance.require = require; - instance.export = function(exports) { - $.extend(instance, exports); - }; - - //Setup the module - try { - module(instance, require, $); - } catch(err) { - console.error('Error while creating module: '+id, err); - } - - //Initialize the module when document is ready - if(!initialized) { - initialModules.push(instance); - } else { - instance.init(); - } - }; - - /** - * Returns a module by its namespace e.g: - * - * For the module humhub.modules.ui.modal you can search: - * - * require('ui.modal'); - * require('modules.ui.modal'); - * require('humhub.modules.ui.modal'); - * - * @param {type} moduleId - * @returns object - the module instance if already initialized else undefined - * - * */ - var require = function(moduleNS) { - var module = resolveNameSpace(moduleNS); - if(!module) { - //TODO: load remote module dependencies - console.warn('No module found for id: '+moduleNS); - } - return module; - }; - - /** - * Search the given module namespace, and creates the given namespace - * if init = true. - * @param {type} typePath the searched module namespace - * @param {Boolean} init - if set to true, creates namespaces if not already present - * @returns object - the given module - */ - var resolveNameSpace = function(typePath, init) { - try { - //cut humhub.modules prefix if present - var moduleSuffix = _cutModulePrefix(typePath); - - //Iterate through the namespace and return the last entry - var result = modules; - $.each(moduleSuffix.split('.'), function(i, subPath) { - if(subPath in result) { - result = result[subPath]; - } else if(init) { - result = result[subPath] = {}; - } else { - result = undefined; //path not found - return false; //leave each loop - } - }); - return result; - } catch(e) { - //TODO: handle could not resolve type/namespace error - return; - } - }; - - /** - * Config implementation - */ - - var config = { - get : function(module, key, defaultVal) { - if(arguments.length === 1) { - return this.getModuleConfig(module); - } else if(_isDefined(key)) { - var result = this.getModuleConfig(module)[key]; - return (_isDefined(result)) ? result : defaultVal; - } - }, - getModuleConfig: function(module) { - if(!this[module]) { - this[module] = {}; - } - return this[module]; - }, - - is : function(module, key, defaultVal) { - return this.get(module, key, defaultVal) === true; - }, - - set : function(moduleId, key, value) { - //Moduleid with multiple values - if(arguments.length === 1) { - var that = this; - $.each(moduleId, function(moduleKey, config) { - that.set(moduleKey, config); - }); - }else if(arguments.length === 2) { - $.extend(this.getModuleConfig(moduleId), key); - } else if(arguments.length === 3) { - this.getModuleConfig(moduleId)[key] = value; - } - } - }; - - /** - * Cuts the prefix humub.modules or modules. from the given value. - * @param {type} value - * @returns {unresolved} - */ - var _cutModulePrefix = function(value) { - return _cutPrefix(_cutPrefix(value, 'humhub.'), 'modules.'); - }; - - /** - * Cuts a prefix from a string, this is already available in humhub.util but - * this is not accessible here. - * - * @param {type} value - * @param {type} prefix - * @returns {unresolved} - */ - var _cutPrefix = function(value, prefix) { - if(!_startsWith(value, prefix)) { - return value; - } - return value.substring(prefix.length, value.length); - }; - - /** - * Checks if a string strats with a given prefix - * @param {type} val - * @param {type} prefix - * @returns {Boolean} - */ - var _startsWith = function(val, prefix) { - if(!val || !prefix) { - return false; - } - return val.indexOf(prefix) === 0; - }; - - var _isDefined = function(obj) { - return typeof obj !== 'undefined'; - }; - - //Initialize all initial modules - $(document).ready(function() { - $.each(initialModules, function(i, module) { - if(module.init) { - try { - module.init(); - } catch(err) { - console.error('Could not initialize module: '+module.id, err); - } - } - initialized = true; - console.log('Module initialized: '+module.id); - }); - }); - - - - return { - initModule: initModule, - modules: modules, - config: config - }; -})($);;/** - * Util module with sub module for object and string utility functions - */ -humhub.initModule('util', function(module, require, $) { - var object = { - isFunction: function (obj) { - return $.isFunction(obj); - }, - isObject: function (obj) { - return $.isPlainObject(obj); - }, - isJQuery: function (obj) { - return this.isDefined(obj) && obj.jquery; - }, - isArray: function(obj) { - return $.isArray(obj); - }, - isEmpty: function(obj) { - return $.isEmptyObject(obj); - }, - isString: function (obj) { - return typeof obj === 'string'; - }, - isNumber: function (n) { - return this.isDefined(n) && !isNaN(parseFloat(n)) && isFinite(n); - }, - isBoolean: function (obj) { - return typeof obj === 'boolean'; - }, - isDefined: function (obj) { - if (arguments.length > 1) { - var result = true; - var that = this; - this.each(arguments, function (index, value) { - if (!that.isDefined(value)) { - return false; - } - }); - - return result; - } - return typeof obj !== 'undefined'; - }, - inherits: function(Sub, Parent) { - Sub.prototype = Object.create(Parent.prototype); - Sub._super = Parent.prototype; - } - }; - - var string = { - cutprefix : function(val, prefix) { - if(!this.startsWith(val, prefix)) { - return val; - } - return val.substring(prefix.length, val.length); - }, - cutsuffix : function(val, suffix) { - return val.slice(0, suffix.length * -1); - }, - startsWith : function(val, prefix) { - if(!object.isDefined(val) || !object.isDefined(prefix)) { - return false; - } - return val.indexOf(prefix) === 0; - }, - endsWith : function(val, suffix) { - if(!object.isDefined(val) || !object.isDefined(suffix)) { - return false; - } - return val.indexOf(suffix, val.length - suffix.length) !== -1; - } - }; - - module.export({ - object: object, - string: string - }); -});;/** - * This module manages UI-Additions registered by other modules. Additions can be applied to DOM elements - * and are used to add a specific behaviour or manipulate the element itself. e.g: Richtext, Autosize input... - * - * An addition can be registered for a specific selector e.g: - * It is possible to register multiple additions for the same selector. - */ -humhub.initModule('additions', function(module, require, $) { - var _additions = {}; - - /** - * Registers an addition for a given jQuery selector. There can be registered - * multiple additions for the same selector. - * - * @param {string} selector jQuery selector - * @param {function} addition addition function - * @returns {undefined} - */ - module.registerAddition = function (selector, addition) { - if(!_additions[selector]) { - _additions[selector] = []; - } - - _additions[selector].push(addition); - }; - - /** - * Applies all matched additions to the given element and its children - * @param {type} element - * @returns {undefined} - */ - module.applyTo = function(element) { - var $element = $(element); - $.each(_additions, function(selector, additions) { - $.each(additions, function(i, addition) { - $.each($element.find(selector).addBack(selector), function() { - try { - var $match = $(this); - addition.apply($match, [$match, $element]); - } catch(e) { - console.error('Error while applying addition '+addition+' on selector '+selector); - } - }); - }); - }); - }; - - module.init = function() { - //TODO: apply to html on startup, the problem is this could crash legacy code. - }; -});;/** - * Manages the client/server communication. Handles humhub json api responses and - * pjax requests. - */ -humhub.initModule('client', function (module, require, $) { - var object = require('util').object; - - var init = function() { - /*$.ajaxPrefilter('html', function(options, originalOptions, jqXHR) { - debugger; - console.log(options); - var pjaxHandler = options.success; - options.success = function(result, textStatus, xhr) { - console.log(result); - pjaxHandler(result, textStatus, xhr); - }; - options.error = function(err) { - debugger; - }; - }); - - ///TEESSS - $.pjax.defaults.maxCacheLength = 0; - $('a.dashboard').on('click', function(evt) { - debugger; - evt.preventDefault(); - $.pjax({url:$(this).attr('href'), container: '#main-content', maxCacheLength:0, timeout:2000}); - });*/ - } - /** - * Response Wrapper Object for easily accessing common data - */ - var Response = function (data) { - $.extend(this, data); - }; - - /** - * Checks if the response is a confirmation of the request - * @returns {Boolean} - */ - Response.prototype.isConfirmation = function () { - return this.getStatus() === 0; - }; - - //TODO: isValidationError status 2 - - /** - * Checks if the response is marke - * @returns {humhub.client_L5.Response.data.status|Boolean} - */ - Response.prototype.isError = function () { - return this.getStatus() > 0 || this.getErrors().length; - }; - - Response.prototype.getStatus = function () { - return (this.status) ? this.status : -1; - }; - - Response.prototype.getFirstError = function() { - var errors = this.getErrors(); - if(errors.length) { - return errors[0]; - } - }; - - Response.prototype.setAjaxError = function(xhr, errorThrown, textStatus,data , status) { - this.xhr = xhr; - this.textStatus = textStatus; - this.status = status || xhr.status; - this.errors = [errorThrown]; - }; - - /** - * Returns an array of errors or an empty array so getErrors().length is always - * safe. - * @returns {array} error array or empty array - */ - Response.prototype.getErrors = function () { - var errors = this.errors || []; - return (object.isString(errors)) ? [errors] : errors; - }; - - Response.prototype.toString = function () { - return "{ status: " + this.getStatus() + " error: " + this.getErrors() + " data: " + this.getContent() + " }"; - }; - - var submit = function ($form, cfg) { - var cfg = cfg || {}; - $form = object.isString($form) ? $($form) : $form; - cfg.type = $form.attr('method') || 'post'; - cfg.data = $form.serialize(); - ajax($form.attr('action'), cfg); - }; - - var post = function(path, cfg) { - var cfg = cfg || {}; - cfg.type = 'POST'; - cfg.method = 'POST'; - return ajax(path, cfg); - }; - - var ajax = function (path, cfg) { - return new Promise(function(resolve, reject) { - cfg = cfg || {}; - - //Wrap the actual error handler with our own and call - var errorHandler = cfg.error; - var error = function (xhr, textStatus, errorThrown, data, status) { - //Textstatus = "timeout", "error", "abort", "parsererror", "application" - if (errorHandler && object.isFunction(errorHandler)) { - var response = new Response(); - response.setAjaxError(xhr, errorThrown, textStatus, data, status); - errorHandler(response); - } - reject(xhr, textStatus, errorThrown, data, status); - }; - - var successHandler = cfg.success; - var success = function (json, textStatus, xhr) { - var response = new Response(json); - if (response.isError()) { //Application errors - return error(xhr, "application", response.getErrors(), json, response.getStatus() ); - } else if (successHandler) { - response.textStatus = textStatus; - response.xhr = xhr; - successHandler(response); - } - resolve(response); - }; - - //Overwriting the handler with our wrapper handler - cfg.success = success; - cfg.error = error; - cfg.url = path; - - //Setting some default values - cfg.dataType = cfg.dataType || "json"; - - $.ajax(cfg); - }); - }; - - module.export({ - ajax: ajax, - post: post, - submit: submit, - init: init - }); -}); - -/** - * - var handleResponse = function (json, callback) { - var response = new Response(json); - if (json.content) { - response.$content = $('
' + json.content + '
'); - - //Find all remote scripts and remove them from the partial - var scriptSrcArr = []; - response.$content.find('script[src]').each(function () { - scriptSrcArr.push($(this).attr('src')); - $(this).remove(); - }); - - //Load the remote scripts synchronously only if they are not already loaded. - scripts.loadOnceSync(scriptSrcArr, function () { - callback(response); - }); - } else { - callback(response); - } - }; - */;humhub.initModule('ui', function(module, require, $) { - var additions = require('additions'); - module.init = function() { - additions.registerAddition('.autosize', function($match) { - $match.autosize(); - }); - }; -});;/** - * Module for creating an manipulating modal dialoges. - * Normal layout of a dialog: - * - * - * - * @param {type} param1 - * @param {type} param2 - */ -humhub.initModule('ui.modal', function (module, require, $) { - var object = require('util').object; - var additions = require('additions'); - var config = humhub.config.getModuleConfig('ui.modal'); - //Keeps track of all initialized modals - var modals = []; - - var TMPL_MODAL_CONTAINER = ''; - var TMPL_MODAL_HEADER = ''; - var TMPL_MODAL_BODY = ''; - var ERROR_DEFAULT_TITLE = 'Error'; - var ERROR_DEFAULT_MESSAGE = 'An unknown error occured!'; - - /** - * The Modal class can be used to create new modals or manipulate existing modals. - * If the constructor finds an element with the given id we use the existing modal, - * if the id is not already used, we create a new modal dom element. - * - * @param {string} id - id of the modal - */ - var Modal = function (id) { - this.$modal = $('#' + id); - if (!this.$modal.length) { - this.createModal(id); - } - this.initModal(); - modals.push(this); - }; - - /** - * Creates a new modal dom skeleton. - * @param {type} id the modal id - * @returns {undefined} - */ - Modal.prototype.createModal = function (id) { - this.$modal = $(TMPL_MODAL_CONTAINER).attr('id', id); - $('body').append(this.$modal); - }; - - /** - * Initializes default modal events and sets initial data. - * @returns {undefined} - */ - Modal.prototype.initModal = function () { - //Set the loader as default content - this.reset(); - var that = this; - - //Set default modal manipulation event handlers - this.getDialog().on('click', '[data-modal-close]', function () { - that.close(); - }).on('click', '[data-modal-clear-error]', function () { - that.clearErrorMessage(); - }); - }; - - /** - * Closes the modal with fade animation and sets the loader content - * @returns {undefined} - */ - Modal.prototype.close = function () { - var that = this; - this.$modal.fadeOut('fast', function () { - that.$modal.modal('hide'); - that.reset(); - }); - }; - - /** - * Sets the loader content and shows the modal - * @returns {undefined} - */ - Modal.prototype.loader = function () { - this.reset(); - this.show(); - }; - - /** - * Sets the default content (a loader animation) - * @returns {undefined} - */ - Modal.prototype.reset = function () { - this.setBody('
'); - this.isFilled = false; - }; - - /** - * Sets the given content and applies content additions. - * @param {string|jQuery} content - content to be set - * @param {function} callback - callback function is called after html was inserted - * @returns {undefined} - */ - Modal.prototype.content = function (content, callback) { - try { - var that = this; - this.clearErrorMessage(); - this.getContent().html(content).promise().always(function () { - additions.applyTo(that.getContent()); - !callback || callback(this.$modal); - }); - this.isFilled = true; - } catch (err) { - console.error('Error while setting modal content', err); - this.setErrorMessage(err.message); - //We try to apply additions anyway - additions.applyTo(that.$modal); - } - }; - - /** - * Sets an errormessage and title. This function either creates an standalone - * error modal with title and message, or adds/replaces a errorboxmessage to - * already exising and filled modals. - * @param {type} title - * @param {type} message - * @returns {undefined} - */ - Modal.prototype.error = function (title, message) { - - if (arguments.length === 1 && title) { - message = (title.getFirstError) ? title.getFirstError() : title; - title = (title.getErrorTitle) ? title.getErrorTitle() : ERROR_DEFAULT_TITLE; - } - - title = title || ERROR_DEFAULT_TITLE; - message = message || ERROR_DEFAULT_MESSAGE; - - //If there is no content yet we create an error only content - if (!this.isFilled) { - this.clear(); - this.setTitle(title); - this.setBody(''); - this.setErrorMessage(message); - this.show(); - } else { - //TODO: allow to set errorMessage and title even for inline messages - this.setErrorMessage(message); - } - }; - - /** - * Removes existing error messages - * @returns {undefined} - */ - Modal.prototype.clearErrorMessage = function () { - var modalError = this.getErrorMessage(); - if (modalError.length) { - modalError.fadeOut('fast', function () { - modalError.remove(); - }); - } - }; - - /** - * Adds or replaces an errormessagebox - * @param {type} message - * @returns {undefined} - */ - Modal.prototype.setErrorMessage = function (message) { - var $errorMessage = this.getErrorMessage(); - if ($errorMessage.length) { - $errorMessage.css('opacity', 0); - $errorMessage.text(message); - $errorMessage.animate({'opacity': 1}, 'fast'); - } else { - this.getBody().prepend(''); - } - }; - - /** - * Returns the current errormessagebox - * @returns {humhub.ui.modal_L18.Modal.prototype@call;getContent@call;find} - */ - Modal.prototype.getErrorMessage = function () { - return this.getContent().find('.modal-error'); - }; - - /** - * Shows the modal - * @returns {undefined} - */ - Modal.prototype.show = function () { - this.$modal.modal('show'); - }; - - /** - * Clears the modal content - * @returns {undefined} - */ - Modal.prototype.clear = function () { - this.getContent().empty(); - }; - - /** - * Retrieves the modal content jQuery representation - * @returns {humhub.ui.modal_L18.Modal.prototype@pro;$modal@call;find} - */ - Modal.prototype.getContent = function () { - //We use the :first selector since jQuery refused to execute javascript if we set content with inline js - return this.$modal.find('.modal-content:first'); - }; - - /** - * Retrieves the modal dialog jQuery representation - * @returns {humhub.ui.modal_L18.Modal.prototype@pro;$modal@call;find} - */ - Modal.prototype.getDialog = function () { - return this.$modal.find('.modal-dialog'); - }; - - /** - * Searches for forms within the modal - * @returns {humhub.ui.modal_L18.Modal.prototype@pro;$modal@call;find} - */ - Modal.prototype.getForm = function () { - return this.$modal.find('form'); - }; - - /** - * Adds or replaces a modal-title with close button and a title text. - * @param {type} title - * @returns {undefined} - */ - Modal.prototype.setTitle = function (title) { - var $header = this.getHeader(); - if (!$header.length) { - this.getContent().prepend($(TMPL_MODAL_HEADER)); - $header = this.getHeader(); - } - $header.find('.modal-title').html(title); - }; - - /** - * Adds or replaces the current modal-body - * @param {type} content - * @returns {undefined} - */ - Modal.prototype.setBody = function (content) { - var $body = this.getBody(); - if (!$body.length) { - this.getContent().append($(TMPL_MODAL_BODY)); - $body = this.getBody(); - } - $body.html(content); - }; - - /** - * Retrieves the modal-header element - * @returns {humhub.ui.modal_L18.Modal.prototype@pro;$modal@call;find} - */ - Modal.prototype.getHeader = function () { - return this.$modal.find('.modal-header'); - }; - - /** - * Retrieves the modal-body element - * @returns {humhub.ui.modal_L18.Modal.prototype@pro;$modal@call;find} - */ - Modal.prototype.getBody = function () { - return this.$modal.find('.modal-body'); - }; - - var ConfirmModal = function(id, cfg) { - Modal.call(this, id); - }; - - object.inherits(ConfirmModal, Modal); - - ConfirmModal.prototype.open = function(cfg) { - this.clear(); - cfg['header'] = cfg['header'] || config['defaultConfirmHeader']; - cfg['body'] = cfg['body'] || config['defaultConfirmBody']; - cfg['confirmText'] = cfg['confirmText'] || config['defaultConfirmText']; - cfg['cancleText'] = cfg['cancleText'] || config['defaultCancelText']; - this.setTitle(cfg['header']); - this.setBody(cfg['body']); - this.initButtons(cfg); - this.show(); - }; - - ConfirmModal.prototype.clear = function(cfg) { - this.$modal.find('[data-modal-confirm]').off('click'); - this.$modal.find('[data-modal-cancel]').off('click'); - }; - - ConfirmModal.prototype.initButtons = function(cfg) { - //Set button text - var $cancelButton = this.$modal.find('[data-modal-cancel]'); - $cancelButton.text(cfg['cancleText']); - - var $confirmButton = this.$modal.find('[data-modal-confirm]'); - $confirmButton.text(cfg['confirmText']); - - //Init handler - var that = this; - if(cfg['confirm']) { - $confirmButton.one('click', function(evt) { - that.clear(); - cfg['confirm'](evt); - }); - } - - if(cfg['cancel']) { - $cancelButton.one('click', function(evt) { - that.clear(); - cfg['cancel'](evt); - }); - } - - - }; - - module.export({ - init: function () { - module.global = new Modal('globalModal'); - module.globalConfirm = new ConfirmModal('globalModalConfirm'); - module.confirm = function(cfg) { - module.globalConfirm.open(cfg); - }; - }, - Modal: Modal - }); -});;/** - * Thid module can be used by humhub sub modules for registering handlers and serves as core module for executing actions triggered in the gui. - * A module can either register global handler by using the registerHandler and registerAjaxHandler functions or use the content mechanism. - */ -humhub.initModule('actions', function (module, require, $) { - var _handler = {}; - var object = require('util').object; - var string = require('util').string; - var client = require('client'); - - var DATA_COMPONENT = 'action-component'; - var DATA_COMPONENT_SELECTOR = '[data-'+DATA_COMPONENT+']'; - - var Component = function(container) { - if(!container) { - return; - } - this.$ = (object.isString(container)) ? $('#' + container) : container; - this.base = this.$.data(DATA_COMPONENT); - }; - - Component.prototype.data = function(dataSuffix) { - var result = this.$.data(dataSuffix); - if(!result) { - var parentComponent = this.parent(); - if(parentComponent) { - return parentComponent.data(dataSuffix); - } - } - return result; - }; - - Component.prototype.parent = function() { - var $parent = this.$.parent().closest(DATA_COMPONENT_SELECTOR); - if($parent.length) { - try { - var ParentType = require($parent.data(DATA_COMPONENT)); - return new ParentType($parent); - } catch(err) { - console.error('Could not instantiate parent component: '+$parent.data(DATA_COMPONENT)); - } - } - }; - - Component.prototype.children = function() { - var result = []; - this.$.find(DATA_COMPONENT_SELECTOR).each(function() { - var component = Component.getInstance($(this)); - if(component) { - result.push(component); - } - }); - return result; - }; - - Component.prototype.hasAction = function(action) { - return this.actions().indexOf(action) >= 0; - }; - - Component.prototype.actions = function() { - return []; - }; - - Component.getInstance = function($node) { - //Determine closest component node (parent or or given node) - $node = (object.isString($node)) ? $('#'+$node) : $node; - var $componentRoot = ($node.data(DATA_COMPONENT)) ? $node : Component.getClosestComponentNode($node); - - var componentType = $componentRoot.data(DATA_COMPONENT); - - var ComponentType = require(componentType); - if(ComponentType) { - return new ComponentType($componentRoot); - } else { - console.error('Tried to instantiate component with invalid type: '+componentType); - } - }; - - Component.getClosestComponentNode = function($element) { - return $element.closest(DATA_COMPONENT_SELECTOR); - }; - - /** - * Handles the given componentAction event. The event should provide the following properties: - * - * $trigger (required) : the trigger node of the event - * handler (required) : the handler functionn name to be executed on the component - * type (optoinal) : the event type 'click', 'change',... - * - * @param {object} event - event object - * @returns {Boolean} true if the componentAction could be executed else false - */ - Component.handleAction = function(event) { - var component = Component.getInstance(event.$trigger); - if(component) { - //Check if the content instance provides this actionhandler - if(event.handler && component[event.handler]) { - component[event.handler](event); - return true; - } - } - return false; - }; - - /** - * Constructor for initializing the module. - */ - module.init = function () { - //Binding default action types - this.bindAction(document, 'click', '[data-action-click]'); - this.bindAction(document, 'dblclick', '[data-action-dblclick]'); - this.bindAction(document, 'change', '[data-action-mouseout]'); - - //Add addition for loader buttons - require('additions').registerAddition('[data-action-load-button]', function () { - var that = this; - this.on('click.humhub-action-load-button', function (evt) { - if (!that.find('.action-loader').length) { - that.append(''); - } - }); - }); - }; - - /** - * Registers a given handler with the given id. - * - * This handler will be called e.g. after clicking a button with the handler id as - * data-action-click attribute. - * - * The handler can access additional event information through the argument event. - * The this object within the handler will be the trigger of the event. - * - * @param {string} id handler id should contain the module namespace - * @param {function} handler function with one event argument - * @returns {undefined} - */ - module.registerHandler = function (id, handler) { - if (!id) { - return; - } - - if (handler) { - _handler[id] = handler; - } - }; - - /** - * Registers an ajax eventhandler. - * The function can either be called with four arguments (id, successhandler, errorhandler, additional config) - * or with two (id, cfg) where tha handlers are contained in the config object itself. - * - * The successhandler will be called only if the response does not contain any errors or errormessages. - * So the errorhandler is called for application and http errors. - * - * The config can contain additional ajax settings. - * - * @param {type} id - * @param {type} success - * @param {type} error - * @param {type} cfg - * @returns {undefined} - */ - module.registerAjaxHandler = function (id, success, error, cfg) { - cfg = cfg || {}; - if (!id) { - return; - } - - if (object.isFunction(success)) { - cfg.success = success; - cfg.error = error; - } else { - cfg = success; - } - - if (success) { - _handler[id] = function (event) { - var path = $(this).data('action-url-' + event.type) || $(this).data('action-url'); - client.ajax(path, cfg); - }; - } - }; - - /** - * Binds an delegate wrapper event handler to the parent node. This is used to detect action handlers like - * data-action-click events and map the call to either a stand alone handler or a content - * action handler. The trigger of a contentAction has to be contained in a data-content-base node. - * - * This function uses the jQuery event delegation: - * - * $(parent).on(type, selector, function(){...}); - * - * This assures the event binding for dynamic content (ajax content etc..) - * - * @see {@link humhub.modules.content.handleAction} - * @param {Node|jQuery} parent - the event target - * @param {string} type - event type e.g. click, change,... - * @param {string} selector - jQuery selector - * @param {string} selector - jQuery selector - */ - module.bindAction = function (parent, type, selector, directHandler) { - parent = parent || document; - var $parent = parent.jquery ? parent : $(parent); - $parent.on(type+'.humhub-action', selector, function (evt) { - evt.preventDefault(); - //The element which triggered the action e.g. a button or link - $trigger = $(this); - - //Get the handler id, either a stand alone handler or a content handler function e.g: 'edit' - var handlerId = $trigger.data('action' + '-' + type); - var event = {type: type, $trigger: $trigger, handler: handlerId}; - - event.finish = function() { - _removeLoaderFromEventTarget(evt); - }; - - //TODO: handle with $.Event - //var event = $.Event(type, {$trigger: $trigger}); - //event.originalEvent = evt; - - //Search and execute a stand alone handler or try to call the content action handler - try { - //Direct action handler - if(object.isFunction(directHandler)) { - directHandler.apply($trigger, [event]); - return; - } - - //Component handler - if(Component.handleAction(event)) { - return; - } - - //Registered handler - if(_handler[handlerId]) { - //Registered action handler - var handler = _handler[handlerId]; - handler.apply($trigger, [event]); - return; - } - - //As last resort we try to call the action by namespace handler - var splittedNS = handlerId.split('.'); - var handler = splittedNS[splittedNS.length - 1]; - var target = require(string.cutsuffix(handlerId, '.' + handler)); - if(object.isFunction(target)) { - target[handler]({type: type, $trigger: $trigger}); - } else { - console.error('Could not determine actionhandler for: '+handlerId); - } - } catch (e) { - //TODO: handle error ! - console.error('Error while handling action event for handler "' + handlerId+'"', e); - _removeLoaderFromEventTarget(evt); - } - }); - }; - - var _removeLoaderFromEventTarget = function (evt) { - if (evt.target) { - $target = $(evt.target); - $loader = $target.find('.action-loader'); - - if ($loader.length) { - $loader.remove(); - } - } - }; - - module.export({ - Component: Component - }); -});;/** - * This module provides an api for handling content objects e.g. Posts, Polls... - * - * @type undefined|Function - */ - -humhub.initModule('content', function(module, require, $) { - var client = require('client'); - var object = require('util').object; - var actions = require('actions'); - var Component = actions.Component; - - var DATA_CONTENT_KEY = "content-key"; - var DATA_CONTENT_EDIT_URL = "content-edit-url"; - var DATA_CONTENT_SAVE_SELECTOR = "[data-content-save]"; - var DATA_CONTENT_DELETE_URL = "content-delete-url"; - - - var Content = function(container) { - Component.call(this, container); - }; - - object.inherits(Content, Component); - - Content.prototype.actions = function() { - return ['create','edit','delete']; - }; - - Content.prototype.getKey = function () { - return this.$.data(DATA_CONTENT_KEY); - }; - - Content.prototype.create = function (addContentHandler) { - //Note that this Content won't have an id, so the backend will create an instance - if(this.hasAction('create')) { - return; - } - - this.edit(addContentHandler); - }; - - Content.prototype.edit = function (successHandler) { - if(!this.hasAction('edit')) { - return; - } - - var editUrl = this.data(DATA_CONTENT_EDIT_URL); - var contentId = this.getKey(); - var modal = require('ui.modal').global; - - if(!editUrl) { - //Todo: handle error - console.error('No editUrl found for edit content action editUrl: '+editUrl+ ' contentId '+contentId); - return; - } - - var that = this; - - client.ajax(editUrl, { - data: { - 'id' : contentId - }, - beforeSend: function() { - modal.loader(); - }, - success: function(response) { - //Successfully retrieved the edit form, now show it within a modal - modal.content(response.getContent(), function() { - //Bind direct action handler we could use a global registeredHandler but this is more efficient - actions.bindAction(modal.getBody(), 'click', DATA_CONTENT_SAVE_SELECTOR, function(event) { - client.submit(modal.getForm(), { - success : function(response) { - if(object.isFunction(successHandler)) { - if(successHandler(response, modal)) {modal.close();}; - } else { - that.replaceContent(response.getContent()); - //TODO: check for content.highlight - modal.close(); - } - event.finish(); - }, - error : function(error) { - //TODO: handle error - modal.error(error); - console.error('Error while submitting form :'+error); - event.finish(); - } - }); - }); - }); - }, - error: function(errResponse) { - modal.error(errResponse); - console.log('Error occured while editing content: '+errResponse.getFirstError()); - //Todo: handle error - } - }); - }; - - Content.prototype.delete = function () { - if(!this.hasAction('delete')) { - return; - } - - var that = this; - require('ui.modal').confirm({ - confirm : function() { - var url = that.data(DATA_CONTENT_DELETE_URL); - if(url) { - client.post(url, { - data: { - id: that.getKey() - } - }).then(function(response) { - that.remove(); - }).catch(function(err) { - console.error('Error removing content',err); - }); - } else { - console.error('Content delete was called, but no url could be determined for '+this.base); - } - } - }); - - return; - }; - - Content.prototype.remove = function() { - var that = this; - this.$.animate({ height: 'toggle', opacity: 'toggle' }, 'fast', function() { - that.$.remove(); - //TODO: fire global event - }); - }; - - module.export({ - Content : Content - }); -});;/** - * Core module for managing Streams and StreamItems - * @type Function - */ -humhub.initModule('stream', function (module, require, $) { - - var util = require('util'); - var object = util.object; - var string = util.string; - var client = require('client'); - var Content = require('content').Content; - - /** - * Number of initial stream enteis loaded when stream is initialized. - * @type Number - */ - var STREAM_INIT_COUNT = 8; - - /** - * Number of stream entries loaded with each request (except initial request) - * @type Number - */ - var STREAM_LOAD_COUNT = 4; - - /** - * Set on the stream root node to identify a stream. The value of this data - * attribute contains the stream url for loading new entries. - * @type String - */ - var DATA_STREAM_SELECTOR = '[data-stream]'; - - /** - * Set on a stream entry root node to identify stream-entries. - * @type String - */ - var DATA_STREAM_ENTRY_SELECTOR = '[data-stream-entry]'; - - /** - * If a data-stream-contentid is set on the stream root only one entry will - * be loaded. e.g. for permlinks - * @type String - */ - var DATA_STREAM_CONTENTID = 'stream-contentid'; - - - //TODO: load streamUrl from config - //TODO: readonly - - /** - * Represents an stream entry within a stream. - * @param {type} id - * @returns {undefined} - */ - var StreamEntry = function (id) { - Content.call(this, id); - }; - - object.inherits(StreamEntry, Content); - - StreamEntry.prototype.actions = function () { - return ['delete', 'edit']; - }; - - StreamEntry.prototype.delete = function () { - var content = this.getContentComponent(); - if (content && content.delete) { - //TODO: modalconfirm - content.delete(); - } else { - StreamEntry._super.delete.call(this); - } - }; - - StreamEntry.prototype.getContentComponent = function () { - var children = this.children(); - return children.length ? children[0] : undefined; - }; - - StreamEntry.prototype.reload = function () { - getStream().reload(this); - }; - - StreamEntry.prototype.edit = function () { - //Search for data-content-edit-url on root. - //Call this url with data-content-key - //Trigger delete event - }; - - /** - * Stream implementation. - * - * @param {type} container id or jQuery object of the stream container - * @returns {undefined} - */ - var Stream = function (container) { - Content.call(this, container); - - //If a contentId is set on the stream root we will only show the single content - if (this.$.data(DATA_STREAM_CONTENTID)) { - this.contentId = parseInt(this.$.data(DATA_STREAM_CONTENTID)); - } - - this.$stream = this.$.find(".s2_stream"); - - //Cache some stream relevant data/nodes - this.url = this.$.data('stream'); //TODO: set this in config instead of data field - this.$loader = this.$stream.find(".streamLoader"); - this.$content = this.$stream.find('.s2_streamContent'); - this.$filter = $('.wallFilterPanel'); - - //TODO: make this configurable - this.filters = []; - this.sort = "c"; - }; - - object.inherits(Stream, Content); - - Stream.prototype.getContentActions = function () { - return []; - }; - - /** - * Initializes the stream, by clearing the stream and reloading initial stream entries, - * this should be called if any filter/sort settings are changed or the stream - * needs an reload. - * - * @returns {humhub.stream_L5.Stream.prototype} - */ - Stream.prototype.init = function () { - this.clear(); - this.$stream.show(); - if (this.isShowSingleEntry()) { - this.loadSingleEntry(this.contentId); - } else { - this.loadEntries(STREAM_INIT_COUNT).then(function() { - /** - * TODO: REWRITE OLD INITPLUGINS!!! - */ - initPlugins(); - }); - } - return this; - }; - - Stream.prototype.clear = function () { - this.lastEntryLoaded = false; - this.readOnly = false; - this.loading = false; - this.$.find(".s2_streamContent").empty(); - this.$.find(".s2_stream").hide(); - this.$.find(".s2_single").hide(); - this.$.find(".streamLoader").hide(); - this.$.find(".emptyStreamMessage").hide(); - this.$.find(".emptyFilterStreamMessage").hide(); - this.$.find('.back_button_holder').hide(); - this.$filter.hide(); - }; - - Stream.prototype.loadSingleEntry = function (contentId) { - this.$.find('.back_button_holder').show(); - this.loadEntries(1, (contentId + 1), ''); - }; - - Stream.prototype.reloadEntry = function (entry) { - var that = this; - return new Promise(function (resolve, reject) { - entry = (entry instanceof StreamEntry) ? entry : that.getEntry(entry); - - if (!entry) { - console.warn('Attempt to reload of non existent entry: ' + entry); - reject(); - return; - } - - var contentId = entry.getKey(); - return that._load(1, (contentId + 1), '').then(function (response) { - if (response.content[contentId]) { - entry.replaceContent(response.content[contentId].output); - resolve(entry); - } else { - console.warn('Reload failed: ContentId not found in response: ' + contentId); - reject(); - } - }, reject); - }); - }; - - Stream.prototype.loadEntries = function (limit, from, filter, sort) { - if (this.loading || this.lastEntryLoaded) { - return; - } - - //Initialize loading process - this.$loader.show(); - this.loading = true; - - //Overwrite the stream settings if provided - limit = limit || STREAM_LOAD_COUNT; - from = from || this.getLastContentId(); - filter = filter || this.getFilterString(); - sort = sort || this.sort; - - var that = this; - return new Promise(function (resolve, reject) { - that._load(limit, from, filter, sort).then(function (response) { - that.$loader.hide(); - if (object.isEmpty(response.content)) { - that.lastEntryLoaded = true; - $('#btn-load-more').hide(); - } else { - that.lastEntryLoaded = response.is_last; - that.appendEntries(response); - } - - that.loading = false; - that.onChange(); - resolve(); - }).catch(function (err) { - //TODO: handle error - that.loading = false; - that.$loader.hide(); - reject(); - }); - }); - }; - - Stream.prototype._load = function (limit, from, filter, sort) { - return client.ajax(this.url, { - data: { - filters: filter, - sort: sort, - from: from, - limit: limit - } - }); - }; - - Stream.prototype.getLastContentId = function () { - var $lastEntry = this.$stream.find(DATA_STREAM_ENTRY_SELECTOR).last(); - if ($lastEntry.length) { - return $lastEntry.data(DATA_STREAM_CONTENTID); - } - }; - - Stream.prototype.appendEntries = function (response) { - var that = this; - var result = ''; - $.each(response.contentIds, function (i, key) { - var $entry = that.getEntry(key); - if ($entry.length) { - $entry.remove(); - } - result += response.content[key].output; - }); - return this.$content.append(result); - }; - - /** - * Fired when new entries are shown - */ - Stream.prototype.onChange = function () { - if (this.readOnly) { - $('.wallReadOnlyHide').hide(); - $('.wallReadOnlyShow').show(); - } else { - $('.wallReadOnlyShow').hide(); - } - - var hasEntries = this.hasEntries(); - if (!hasEntries && !this.hasFilter()) { - this.$.find('.emptyStreamMessage').show(); - this.$filter.hide(); - } else if (!hasEntries) { - this.$.find('.emptyFilterStreamMessage').hide(); - } else if (!this.isShowSingleEntry()) { - this.$filter.show(); - this.$.find('.emptyStreamMessage').hide(); - this.$.find('.emptyFilterStreamMessage').hide(); - } - - this.$entryCache = this.getEntryNodes(); - - //TODO: fire global event - }; - - Stream.prototype.isShowSingleEntry = function () { - return object.isDefined(this.contentId); - }; - - Stream.prototype.hasEntries = function () { - return this.getEntryCount() > 0; - }; - - Stream.prototype.getEntryCount = function () { - return this.$.find(DATA_STREAM_ENTRY_SELECTOR).length; - }; - - Stream.prototype.getEntryNodes = function () { - return this.$.find(DATA_STREAM_ENTRY_SELECTOR); - }; - - Stream.prototype.hasFilter = function () { - return this.filters.length > 0; - }; - - Stream.prototype.getFilterString = function () { - var result = ''; - $.each(this.filters, function (i, filter) { - result += filter + ','; - }); - - return string.cutsuffix(result, ','); - }; - - Stream.prototype.setFilter = function (filterId) { - if (this.filters.indexOf(filterId) < 0) { - this.filters.push(filterId); - } - }; - - Stream.prototype.unsetFilter = function (filterId) { - var index = this.filters.indexOf(filterId); - if (index > -1) { - this.filters.splice(index, 1); - } - }; - - Stream.prototype.getEntry = function (key) { - return new StreamEntry(this.$.find(DATA_STREAM_ENTRY_SELECTOR+'[data-content-key="' + key + '"]')); - }; - - Stream.prototype.getEntryByNode = function ($childNode) { - return new StreamEntry($childNode.closest(DATA_STREAM_ENTRY_SELECTOR)); - }; - - var getStream = function () { - if (!module.instance) { - var $stream = $(DATA_STREAM_SELECTOR).first(); - module.instance = $stream.length ? new Stream($stream) : undefined; - } - return module.instance; - }; - - var getEntry = function (id) { - return module.getStream().getEntry(id); - }; - - var init = function () { - var stream = getStream(); - - if (!stream) { - console.log('Non-Stream Page!'); - return; - } - - stream.init(); - - var lastKey; - $(window).scroll(function () { - var $window = $(window); - var scrollTop = $window.scrollTop(); - var windowHeight = $window.height(); - if (scrollTop === ($(document).height() - $window.height())) { - if (stream && !stream.loading && !stream.isShowSingleEntry() && !stream.lastEntryLoaded) { - stream.loadEntries(); - } - } - - // Defines our base y position for changing the current entry - var yLimit = scrollTop + (windowHeight / 2); - - // Get id of current scroll item - //TODO: chache the entry nodes ! - var matchingNodes = stream.$entryCache.map(function () { - var $this = $(this); - if ($this.offset().top < yLimit) { - return $this; - } - }); - - // Get the id of the current element - var $current = matchingNodes[matchingNodes.length - 1]; - var currentKey = $current && $current.length ? $current.data('content-key') : ""; - - if (lastKey !== currentKey) { - lastKey = currentKey; - // Set/remove active class - console.log(currentKey); - } - }); - - stream.$.on('click', '.singleBackLink', function () { - stream.contentId = undefined; - stream.init(); - $(this).hide(); - }); - - initFilterNav(); - }; - - var initFilterNav = function () { - $(".wallFilter").click(function () { - var $filter = $(this); - var checkboxi = $filter.children("i"); - checkboxi.toggleClass('fa-square-o').toggleClass('fa-check-square-o'); - if (checkboxi.hasClass('fa-check-square-o')) { - getStream().setFilter($filter.attr('id').replace('filter_', '')); - } else { - getStream().unsetFilter($filter.attr('id').replace('filter_', '')); - } - getStream().init(); - }); - - $(".wallSorting").click(function () { - var newSortingMode = $(this).attr('id'); - - // uncheck all sortings - $(".wallSorting").find('i') - .removeClass('fa-check-square-o') - .addClass('fa-square-o'); - - // check current sorting mode - $("#" + newSortingMode).children("i") - .removeClass('fa-square-o') - .addClass('fa-check-square-o'); - - // remove sorting id append - newSortingMode = newSortingMode.replace('sorting_', ''); - - // Switch sorting mode and reload stream - getStream().sort = newSortingMode; - getStream().init(); - }); - }; - - module.export({ - StreamEntry: StreamEntry, - Stream: Stream, - getStream: getStream, - getEntry: getEntry, - init: init - }); -}); - -/* TODO: - Stream.prototype.wallStick = function (url) { - $.ajax({ - dataType: "json", - type: 'post', - url: url - }).done(function (data) { - if (data.success) { - if (currentStream) { - $.each(data.wallEntryIds, function (k, wallEntryId) { - currentStream.deleteEntry(wallEntryId); - currentStream.prependEntry(wallEntryId); - }); - $('html, body').animate({scrollTop: 0}, 'slow'); - } - } else { - alert(data.errorMessage); - } - }); - }; - - Stream.prototype.wallUnstick = function (url) { - $.ajax({ - dataType: "json", - type: 'post', - url: url - }).done(function (data) { - if (data.success) { - //Reload the whole stream, since we have to reorder the entries - currentStream.showStream(); - } - }); - }; - - /** - * Click Handler for Archive Link of Wall Posts - * (archiveLink.php) - * - * @param {type} className - * @param {type} id - - Stream.prototype.wallArchive = function (id) { - - url = wallArchiveLinkUrl.replace('-id-', id); - - $.ajax({ - dataType: "json", - type: 'post', - url: url - }).done(function (data) { - if (data.success) { - if (currentStream) { - $.each(data.wallEntryIds, function (k, wallEntryId) { - //currentStream.reloadWallEntry(wallEntryId); - // fade out post - setInterval(fadeOut(), 1000); - - function fadeOut() { - // fade out current archived post - $('#wallEntry_' + wallEntryId).fadeOut('slow'); - } - }); - } - } - }); - }; - - - /** - * Click Handler for Un Archive Link of Wall Posts - * (archiveLink.php) - * - * @param {type} className - * @param {type} id - - Stream.prototype.wallUnarchive = function (id) { - url = wallUnarchiveLinkUrl.replace('-id-', id); - - $.ajax({ - dataType: "json", - type: 'post', - url: url - }).done(function (data) { - if (data.success) { - if (currentStream) { - $.each(data.wallEntryIds, function (k, wallEntryId) { - currentStream.reloadWallEntry(wallEntryId); - }); - - } - } - }); - }; - - - /* - module.StreamItem.prototype.highlightContent = function () { - var $content = this.getContent(); - $content.addClass('highlight'); - $content.delay(200).animate({backgroundColor: 'transparent'}, 1000, function () { - $content.removeClass('highlight'); - $content.css('backgroundColor', ''); - }); - }; - */ \ No newline at end of file diff --git a/js/dist/humhub.all.min.js b/js/dist/humhub.all.min.js deleted file mode 100644 index f5999b2620..0000000000 --- a/js/dist/humhub.all.min.js +++ /dev/null @@ -1 +0,0 @@ -var humhub=humhub||function(a){var b={},c=[],d=!1,e=function(b,e){var h=g(b,!0);h.id="humhub.modules."+i(b),h.require=f,h["export"]=function(b){a.extend(h,b)};try{e(h,f,a)}catch(j){console.error("Error while creating module: "+b,j)}d?h.init():c.push(h)},f=function(a){var b=g(a);return b||console.warn("No module found for id: "+a),b},g=function(c,d){try{var e=i(c),f=b;return a.each(e.split("."),function(a,b){if(b in f)f=f[b];else{if(!d)return f=void 0,!1;f=f[b]={}}}),f}catch(g){return}},h={get:function(a,b,c){if(1===arguments.length)return this.getModuleConfig(a);if(l(b)){var d=this.getModuleConfig(a)[b];return l(d)?d:c}},getModuleConfig:function(a){return this[a]||(this[a]={}),this[a]},is:function(a,b,c){return this.get(a,b,c)===!0},set:function(b,c,d){if(1===arguments.length){var e=this;a.each(b,function(a,b){e.set(a,b)})}else 2===arguments.length?a.extend(this.getModuleConfig(b),c):3===arguments.length&&(this.getModuleConfig(b)[c]=d)}},i=function(a){return j(j(a,"humhub."),"modules.")},j=function(a,b){return k(a,b)?a.substring(b.length,a.length):a},k=function(a,b){return a&&b?0===a.indexOf(b):!1},l=function(a){return"undefined"!=typeof a};return a(document).ready(function(){a.each(c,function(a,b){if(b.init)try{b.init()}catch(c){console.error("Could not initialize module: "+b.id,c)}d=!0,console.log("Module initialized: "+b.id)})}),{initModule:e,modules:b,config:h}}($);humhub.initModule("util",function(a,b,c){var d={isFunction:function(a){return c.isFunction(a)},isObject:function(a){return c.isPlainObject(a)},isJQuery:function(a){return this.isDefined(a)&&a.jquery},isArray:function(a){return c.isArray(a)},isEmpty:function(a){return c.isEmptyObject(a)},isString:function(a){return"string"==typeof a},isNumber:function(a){return this.isDefined(a)&&!isNaN(parseFloat(a))&&isFinite(a)},isBoolean:function(a){return"boolean"==typeof a},isDefined:function(a){if(arguments.length>1){var b=!0,c=this;return this.each(arguments,function(a,b){return c.isDefined(b)?void 0:!1}),b}return"undefined"!=typeof a},inherits:function(a,b){a.prototype=Object.create(b.prototype),a._super=b.prototype}},e={cutprefix:function(a,b){return this.startsWith(a,b)?a.substring(b.length,a.length):a},cutsuffix:function(a,b){return a.slice(0,-1*b.length)},startsWith:function(a,b){return d.isDefined(a)&&d.isDefined(b)?0===a.indexOf(b):!1},endsWith:function(a,b){return d.isDefined(a)&&d.isDefined(b)?-1!==a.indexOf(b,a.length-b.length):!1}};a["export"]({object:d,string:e})}),humhub.initModule("additions",function(a,b,c){var d={};a.registerAddition=function(a,b){d[a]||(d[a]=[]),d[a].push(b)},a.applyTo=function(a){var b=c(a);c.each(d,function(a,d){c.each(d,function(d,e){c.each(b.find(a).addBack(a),function(){try{var d=c(this);e.apply(d,[d,b])}catch(f){console.error("Error while applying addition "+e+" on selector "+a)}})})})},a.init=function(){}}),humhub.initModule("client",function(a,b,c){var d=b("util").object,e=function(){},f=function(a){c.extend(this,a)};f.prototype.isConfirmation=function(){return 0===this.getStatus()},f.prototype.isError=function(){return this.getStatus()>0||this.getErrors().length},f.prototype.getStatus=function(){return this.status?this.status:-1},f.prototype.getFirstError=function(){var a=this.getErrors();return a.length?a[0]:void 0},f.prototype.setAjaxError=function(a,b,c,d,e){this.xhr=a,this.textStatus=c,this.status=e||a.status,this.errors=[b]},f.prototype.getErrors=function(){var a=this.errors||[];return d.isString(a)?[a]:a},f.prototype.toString=function(){return"{ status: "+this.getStatus()+" error: "+this.getErrors()+" data: "+this.getContent()+" }"};var g=function(a,b){var b=b||{};a=d.isString(a)?c(a):a,b.type=a.attr("method")||"post",b.data=a.serialize(),i(a.attr("action"),b)},h=function(a,b){var b=b||{};return b.type="POST",b.method="POST",i(a,b)},i=function(a,b){return new Promise(function(e,g){b=b||{};var h=b.error,i=function(a,b,c,e,i){if(h&&d.isFunction(h)){var j=new f;j.setAjaxError(a,c,b,e,i),h(j)}g(a,b,c,e,i)},j=b.success,k=function(a,b,c){var d=new f(a);return d.isError()?i(c,"application",d.getErrors(),a,d.getStatus()):(j&&(d.textStatus=b,d.xhr=c,j(d)),void e(d))};b.success=k,b.error=i,b.url=a,b.dataType=b.dataType||"json",c.ajax(b)})};a["export"]({ajax:i,post:h,submit:g,init:e})}),humhub.initModule("ui",function(a,b,c){var d=b("additions");a.init=function(){d.registerAddition(".autosize",function(a){a.autosize()})}}),humhub.initModule("ui.modal",function(a,b,c){var d=b("util").object,e=b("additions"),f=humhub.config.getModuleConfig("ui.modal"),g=[],h='',i='',j='',k="Error",l="An unknown error occured!",m=function(a){this.$modal=c("#"+a),this.$modal.length||this.createModal(a),this.initModal(),g.push(this)};m.prototype.createModal=function(a){this.$modal=c(h).attr("id",a),c("body").append(this.$modal)},m.prototype.initModal=function(){this.reset();var a=this;this.getDialog().on("click","[data-modal-close]",function(){a.close()}).on("click","[data-modal-clear-error]",function(){a.clearErrorMessage()})},m.prototype.close=function(){var a=this;this.$modal.fadeOut("fast",function(){a.$modal.modal("hide"),a.reset()})},m.prototype.loader=function(){this.reset(),this.show()},m.prototype.reset=function(){this.setBody('
'),this.isFilled=!1},m.prototype.content=function(a,b){try{var c=this;this.clearErrorMessage(),this.getContent().html(a).promise().always(function(){e.applyTo(c.getContent()),!b||b(this.$modal)}),this.isFilled=!0}catch(d){console.error("Error while setting modal content",d),this.setErrorMessage(d.message),e.applyTo(c.$modal)}},m.prototype.error=function(a,b){1===arguments.length&&a&&(b=a.getFirstError?a.getFirstError():a,a=a.getErrorTitle?a.getErrorTitle():k),a=a||k,b=b||l,this.isFilled?this.setErrorMessage(b):(this.clear(),this.setTitle(a),this.setBody(""),this.setErrorMessage(b),this.show())},m.prototype.clearErrorMessage=function(){var a=this.getErrorMessage();a.length&&a.fadeOut("fast",function(){a.remove()})},m.prototype.setErrorMessage=function(a){var b=this.getErrorMessage();b.length?(b.css("opacity",0),b.text(a),b.animate({opacity:1},"fast")):this.getBody().prepend('")},m.prototype.getErrorMessage=function(){return this.getContent().find(".modal-error")},m.prototype.show=function(){this.$modal.modal("show")},m.prototype.clear=function(){this.getContent().empty()},m.prototype.getContent=function(){return this.$modal.find(".modal-content:first")},m.prototype.getDialog=function(){return this.$modal.find(".modal-dialog")},m.prototype.getForm=function(){return this.$modal.find("form")},m.prototype.setTitle=function(a){var b=this.getHeader();b.length||(this.getContent().prepend(c(i)),b=this.getHeader()),b.find(".modal-title").html(a)},m.prototype.setBody=function(a){var b=this.getBody();b.length||(this.getContent().append(c(j)),b=this.getBody()),b.html(a)},m.prototype.getHeader=function(){return this.$modal.find(".modal-header")},m.prototype.getBody=function(){return this.$modal.find(".modal-body")};var n=function(a,b){m.call(this,a)};d.inherits(n,m),n.prototype.open=function(a){this.clear(),a.header=a.header||f.defaultConfirmHeader,a.body=a.body||f.defaultConfirmBody,a.confirmText=a.confirmText||f.defaultConfirmText,a.cancleText=a.cancleText||f.defaultCancelText,this.setTitle(a.header),this.setBody(a.body),this.initButtons(a),this.show()},n.prototype.clear=function(a){this.$modal.find("[data-modal-confirm]").off("click"),this.$modal.find("[data-modal-cancel]").off("click")},n.prototype.initButtons=function(a){var b=this.$modal.find("[data-modal-cancel]");b.text(a.cancleText);var c=this.$modal.find("[data-modal-confirm]");c.text(a.confirmText);var d=this;a.confirm&&c.one("click",function(b){d.clear(),a.confirm(b)}),a.cancel&&b.one("click",function(b){d.clear(),a.cancel(b)})},a["export"]({init:function(){a.global=new m("globalModal"),a.globalConfirm=new n("globalModalConfirm"),a.confirm=function(b){a.globalConfirm.open(b)}},Modal:m})}),humhub.initModule("actions",function(a,b,c){var d={},e=b("util").object,f=b("util").string,g=b("client"),h="action-component",i="[data-"+h+"]",j=function(a){a&&(this.$=e.isString(a)?c("#"+a):a,this.base=this.$.data(h))};j.prototype.data=function(a){var b=this.$.data(a);if(!b){var c=this.parent();if(c)return c.data(a)}return b},j.prototype.parent=function(){var a=this.$.parent().closest(i);if(a.length)try{var c=b(a.data(h));return new c(a)}catch(d){console.error("Could not instantiate parent component: "+a.data(h))}},j.prototype.children=function(){var a=[];return this.$.find(i).each(function(){var b=j.getInstance(c(this));b&&a.push(b)}),a},j.prototype.hasAction=function(a){return this.actions().indexOf(a)>=0},j.prototype.actions=function(){return[]},j.getInstance=function(a){a=e.isString(a)?c("#"+a):a;var d=a.data(h)?a:j.getClosestComponentNode(a),f=d.data(h),g=b(f);return g?new g(d):void console.error("Tried to instantiate component with invalid type: "+f)},j.getClosestComponentNode=function(a){return a.closest(i)},j.handleAction=function(a){var b=j.getInstance(a.$trigger);return b&&a.handler&&b[a.handler]?(b[a.handler](a),!0):!1},a.init=function(){this.bindAction(document,"click","[data-action-click]"),this.bindAction(document,"dblclick","[data-action-dblclick]"),this.bindAction(document,"change","[data-action-mouseout]"),b("additions").registerAddition("[data-action-load-button]",function(){var a=this;this.on("click.humhub-action-load-button",function(b){a.find(".action-loader").length||a.append('')})})},a.registerHandler=function(a,b){a&&b&&(d[a]=b)},a.registerAjaxHandler=function(a,b,f,h){h=h||{},a&&(e.isFunction(b)?(h.success=b,h.error=f):h=b,b&&(d[a]=function(a){var b=c(this).data("action-url-"+a.type)||c(this).data("action-url");g.ajax(b,h)}))},a.bindAction=function(a,g,h,i){a=a||document;var l=a.jquery?a:c(a);l.on(g+".humhub-action",h,function(a){a.preventDefault(),$trigger=c(this);var h=$trigger.data("action-"+g),l={type:g,$trigger:$trigger,handler:h};l.finish=function(){k(a)};try{if(e.isFunction(i))return void i.apply($trigger,[l]);if(j.handleAction(l))return;if(d[h]){var m=d[h];return void m.apply($trigger,[l])}var n=h.split("."),m=n[n.length-1],o=b(f.cutsuffix(h,"."+m));e.isFunction(o)?o[m]({type:g,$trigger:$trigger}):console.error("Could not determine actionhandler for: "+h)}catch(p){console.error('Error while handling action event for handler "'+h+'"',p),k(a)}})};var k=function(a){a.target&&($target=c(a.target),$loader=$target.find(".action-loader"),$loader.length&&$loader.remove())};a["export"]({Component:j})}),humhub.initModule("content",function(a,b,c){var d=b("client"),e=b("util").object,f=b("actions"),g=f.Component,h="content-key",i="content-edit-url",j="[data-content-save]",k="content-delete-url",l=function(a){g.call(this,a)};e.inherits(l,g),l.prototype.actions=function(){return["create","edit","delete"]},l.prototype.getKey=function(){return this.$.data(h)},l.prototype.create=function(a){this.hasAction("create")||this.edit(a)},l.prototype.edit=function(a){if(this.hasAction("edit")){var c=this.data(i),g=this.getKey(),h=b("ui.modal").global;if(!c)return void console.error("No editUrl found for edit content action editUrl: "+c+" contentId "+g);var k=this;d.ajax(c,{data:{id:g},beforeSend:function(){h.loader()},success:function(b){h.content(b.getContent(),function(){f.bindAction(h.getBody(),"click",j,function(b){d.submit(h.getForm(),{success:function(c){e.isFunction(a)?a(c,h)&&h.close():(k.replaceContent(c.getContent()),h.close()),b.finish()},error:function(a){h.error(a),console.error("Error while submitting form :"+a),b.finish()}})})})},error:function(a){h.error(a),console.log("Error occured while editing content: "+a.getFirstError())}})}},l.prototype["delete"]=function(){if(this.hasAction("delete")){var a=this;b("ui.modal").confirm({confirm:function(){var b=a.data(k);b?d.post(b,{data:{id:a.getKey()}}).then(function(b){a.remove()})["catch"](function(a){console.error("Error removing content",a)}):console.error("Content delete was called, but no url could be determined for "+this.base)}})}},l.prototype.remove=function(){var a=this;this.$.animate({height:"toggle",opacity:"toggle"},"fast",function(){a.$.remove()})},a["export"]({Content:l})}),humhub.initModule("stream",function(a,b,c){var d=b("util"),e=d.object,f=d.string,g=b("client"),h=b("content").Content,i=8,j=4,k="[data-stream]",l="[data-stream-entry]",m="stream-contentid",n=function(a){h.call(this,a)};e.inherits(n,h),n.prototype.actions=function(){return["delete","edit"]},n.prototype["delete"]=function(){var a=this.getContentComponent();a&&a["delete"]?a["delete"]():n._super["delete"].call(this)},n.prototype.getContentComponent=function(){var a=this.children();return a.length?a[0]:void 0},n.prototype.reload=function(){p().reload(this)},n.prototype.edit=function(){};var o=function(a){h.call(this,a),this.$.data(m)&&(this.contentId=parseInt(this.$.data(m))),this.$stream=this.$.find(".s2_stream"),this.url=this.$.data("stream"),this.$loader=this.$stream.find(".streamLoader"),this.$content=this.$stream.find(".s2_streamContent"),this.$filter=c(".wallFilterPanel"),this.filters=[],this.sort="c"};e.inherits(o,h),o.prototype.getContentActions=function(){return[]},o.prototype.init=function(){return this.clear(),this.$stream.show(),this.isShowSingleEntry()?this.loadSingleEntry(this.contentId):this.loadEntries(i).then(function(){initPlugins()}),this},o.prototype.clear=function(){this.lastEntryLoaded=!1,this.readOnly=!1,this.loading=!1,this.$.find(".s2_streamContent").empty(),this.$.find(".s2_stream").hide(),this.$.find(".s2_single").hide(),this.$.find(".streamLoader").hide(),this.$.find(".emptyStreamMessage").hide(),this.$.find(".emptyFilterStreamMessage").hide(),this.$.find(".back_button_holder").hide(),this.$filter.hide()},o.prototype.loadSingleEntry=function(a){this.$.find(".back_button_holder").show(),this.loadEntries(1,a+1,"")},o.prototype.reloadEntry=function(a){var b=this;return new Promise(function(c,d){if(a=a instanceof n?a:b.getEntry(a),!a)return console.warn("Attempt to reload of non existent entry: "+a),void d();var e=a.getKey();return b._load(1,e+1,"").then(function(b){b.content[e]?(a.replaceContent(b.content[e].output),c(a)):(console.warn("Reload failed: ContentId not found in response: "+e),d())},d)})},o.prototype.loadEntries=function(a,b,d,f){if(!this.loading&&!this.lastEntryLoaded){this.$loader.show(),this.loading=!0,a=a||j,b=b||this.getLastContentId(),d=d||this.getFilterString(),f=f||this.sort;var g=this;return new Promise(function(h,i){g._load(a,b,d,f).then(function(a){g.$loader.hide(),e.isEmpty(a.content)?(g.lastEntryLoaded=!0,c("#btn-load-more").hide()):(g.lastEntryLoaded=a.is_last,g.appendEntries(a)),g.loading=!1,g.onChange(),h()})["catch"](function(a){g.loading=!1,g.$loader.hide(),i()})})}},o.prototype._load=function(a,b,c,d){return g.ajax(this.url,{data:{filters:c,sort:d,from:b,limit:a}})},o.prototype.getLastContentId=function(){var a=this.$stream.find(l).last();return a.length?a.data(m):void 0},o.prototype.appendEntries=function(a){var b=this,d="";return c.each(a.contentIds,function(c,e){var f=b.getEntry(e);f.length&&f.remove(),d+=a.content[e].output}),this.$content.append(d)},o.prototype.onChange=function(){this.readOnly?(c(".wallReadOnlyHide").hide(),c(".wallReadOnlyShow").show()):c(".wallReadOnlyShow").hide();var a=this.hasEntries();a||this.hasFilter()?a?this.isShowSingleEntry()||(this.$filter.show(),this.$.find(".emptyStreamMessage").hide(),this.$.find(".emptyFilterStreamMessage").hide()):this.$.find(".emptyFilterStreamMessage").hide():(this.$.find(".emptyStreamMessage").show(),this.$filter.hide()),this.$entryCache=this.getEntryNodes()},o.prototype.isShowSingleEntry=function(){return e.isDefined(this.contentId)},o.prototype.hasEntries=function(){return this.getEntryCount()>0},o.prototype.getEntryCount=function(){return this.$.find(l).length},o.prototype.getEntryNodes=function(){return this.$.find(l)},o.prototype.hasFilter=function(){return this.filters.length>0},o.prototype.getFilterString=function(){var a="";return c.each(this.filters,function(b,c){a+=c+","}),f.cutsuffix(a,",")},o.prototype.setFilter=function(a){this.filters.indexOf(a)<0&&this.filters.push(a)},o.prototype.unsetFilter=function(a){var b=this.filters.indexOf(a);b>-1&&this.filters.splice(b,1)},o.prototype.getEntry=function(a){return new n(this.$.find(l+'[data-content-key="'+a+'"]'))},o.prototype.getEntryByNode=function(a){return new n(a.closest(l))};var p=function(){if(!a.instance){var b=c(k).first();a.instance=b.length?new o(b):void 0}return a.instance},q=function(b){return a.getStream().getEntry(b)},r=function(){var a=p();if(!a)return void console.log("Non-Stream Page!");a.init();var b;c(window).scroll(function(){var d=c(window),e=d.scrollTop(),f=d.height();e===c(document).height()-d.height()&&(!a||a.loading||a.isShowSingleEntry()||a.lastEntryLoaded||a.loadEntries());var g=e+f/2,h=a.$entryCache.map(function(){var a=c(this);return a.offset().top= 0; + }; + + Component.prototype.actions = function () { + return []; + }; + + Component.getInstance = function ($node) { + //Determine closest component node (parent or or given node) + $node = (object.isString($node)) ? $('#' + $node) : $node; + var $componentRoot = ($node.data(DATA_COMPONENT)) ? $node : Component.getClosestComponentNode($node); + + var componentType = $componentRoot.data(DATA_COMPONENT); + + var ComponentType = require(componentType); + if (ComponentType) { + return new ComponentType($componentRoot); + } else { + module.log.error('Tried to instantiate component with invalid type: ' + componentType); + } + }; + + Component.getClosestComponentNode = function ($element) { + return $element.closest(DATA_COMPONENT_SELECTOR); + }; + + /** + * Handles the given componentAction event. The event should provide the following properties: + * + * $trigger (required) : the trigger node of the event + * handler (required) : the handler functionn name to be executed on the component + * type (optoinal) : the event type 'click', 'change',... + * + * @param {object} event - event object + * @returns {Boolean} true if the componentAction could be executed else false + */ + Component.handleAction = function (event) { + var component = Component.getInstance(event.$trigger); + if (component) { + //Check if the content instance provides this actionhandler + if (event.handler && component[event.handler]) { + component[event.handler](event); + return true; + } + } + return false; + }; + + /** + * Constructor for initializing the module. + */ + module.init = function () { + //Binding default action types + this.bindAction(document, 'click', '[data-action-click]'); + this.bindAction(document, 'dblclick', '[data-action-dblclick]'); + this.bindAction(document, 'change', '[data-action-mouseout]'); + + updateBindings(); + + //Add addition for loader buttons + require('ui.additions').registerAddition('[data-action-load-button]', function () { + var that = this; + this.on('click.humhub-action-load-button', function (evt) { + if (!that.find('.action-loader').length) { + that.append(''); + } + }); + }); + }; + + /** + * Registers a given handler with the given id. + * + * This handler will be called e.g. after clicking a button with the handler id as + * data-action-click attribute. + * + * The handler can access additional event information through the argument event. + * The this object within the handler will be the trigger of the event. + * + * @param {string} id handler id should contain the module namespace + * @param {function} handler function with one event argument + * @returns {undefined} + */ + module.registerHandler = function (id, handler) { + if (!id) { + return; + } + + if (handler) { + _handler[id] = handler; + } + }; + + + + /** + * Registers an ajax eventhandler. + * The function can either be called with four arguments (id, successhandler, errorhandler, additional config) + * or with two (id, cfg) where tha handlers are contained in the config object itself. + * + * The successhandler will be called only if the response does not contain any errors or errormessages. + * So the errorhandler is called for application and http errors. + * + * The config can contain additional ajax settings. + * + * @param {type} id + * @param {type} success + * @param {type} error + * @param {type} cfg + * @returns {undefined} + */ + module.registerAjaxHandler = function (id, success, error, cfg) { + cfg = cfg || {}; + if (!id) { + return; + } + + if (object.isFunction(success)) { + cfg.success = success; + cfg.error = error; + } else { + cfg = success; + } + + if (success) { + _handler[id] = function (event) { + var path = $(this).data('action-url-' + event.type) || $(this).data('action-url'); + client.ajax(path, cfg); + }; + } + }; + + var actionBindings = []; + + var updateBindings = function () { + module.log.debug('Update bindings'); + $.each(actionBindings, function (i, binding) { + var $targets = $(binding.selector); + $targets.off(binding.event).on(binding.event, function (evt) { + module.log.debug('Handle direct trigger action', evt); + return binding.handle(evt, $(this)); + }); + $targets.data('action-'+binding.event, true); + }); + }; + + /** + * ActionBinding instances are used to store the binding settings and handling + * binding events. + * + * @param {type} cfg + * @returns {humhub_action_L5.ActionBinding} + */ + var ActionBinding = function (cfg) { + cfg = cfg || {}; + this.parent = cfg.parent; + this.eventType = cfg.type; + this.event = cfg.event; + this.selector = cfg.selector; + this.directHandler = cfg.directHandler; + }; + + /** + * Handles an action event for the given $trigger node. + * + * This handler searches for a valid handler, by checking the following handler types in the given order: + * + * - Direct-ActionHandler is called if a directHandler was given when binding the action. + * - Component-ActionHandler is called if $trigger is part of a component and the component handler can be resolved + * - Global-ActionHandler is called if we find a handler in the _handler array. See registerHandler, registerAjaxHandler + * - Namespace-ActionHandler is called if we can resolve an action by namespace e.g: data-action-click="myModule.myAction" + * + * @param {type} evt the originalEvent + * @param {type} $trigger the jQuery node which triggered the event + * @returns {undefined} + */ + ActionBinding.prototype.handle = function (evt, $trigger) { + module.log.debug('Handle Action', this); + evt.preventDefault(); + + var event = this.createActionEvent(evt, $trigger); + + // Search and execute a stand alone handler or try to call the content action handler + try { + // Check for a direct action handler + if (object.isFunction(this.directHandler)) { + this.directHandler.apply($trigger, [event]); + return; + } + + // Check for a component action handler + if (Component.handleAction(event)) { + return; + } + + // Check for global registered handlers + if (_handler[this.handler]) { + _handler[this.handler].apply($trigger, [event]); + return; + } + + // As last resort we try to call the action by namespace for handlers like humhub.modules.myModule.myAction + var splittedNS = this.handler.split('.'); + var handlerAction = splittedNS[splittedNS.length - 1]; + var target = require(string.cutsuffix(this.handler, '.' + handlerAction)); + + if (object.isFunction(target)) { + target[handlerAction](event); + } else { + module.log.error('actionHandlerNotFound', this, true); + } + } catch (e) { + module.log.error('default', e, true); + _removeLoaderFromEventTarget(evt); + } finally { + // Just to get sure the handler is not called twice. + if(evt.originalEvent) { + evt.originalEvent.actionHandled = true; + } + } + }; + + ActionBinding.prototype.createActionEvent = function (evt, $trigger) { + var event = $.Event(this.eventType); + event.originalEvent = evt; + + // Add some additional action related data to our event. + event.$trigger = $trigger; + + // If the trigger contains an url setting we add it to the event object, and prefer the typed url over the global data-action-url + event.url = $trigger.data('action-' + this.eventType + '-url') || $trigger.data('action-url'); + event.params = $trigger.data('action-' + this.eventType + '-params') || $trigger.data('action-params'); + + //Get the handler id, either a stand alone handler or a content handler function e.g: 'edit' + event.handler = $trigger.data('action' + '-' + this.eventType); + + if ($trigger.is(':submit')) { + event.$form = $trigger.closest('form'); + } + + event.finish = function () { + _removeLoaderFromEventTarget(evt); + }; + + return event; + }; + + var _removeLoaderFromEventTarget = function (evt) { + if (evt && evt.target) { + loader.reset(evt.target); + } + }; + + + /** + * Binds an delegate wrapper event handler to the parent node. This is used to detect action handlers like + * data-action-click events and map the call to either a stand alone handler or a content + * action handler. The trigger of a contentAction has to be contained in a data-content-base node. + * + * This function uses the jQuery event delegation: + * + * $(parent).on(type, selector, function(){...}); + * + * This assures the event binding for dynamic content (ajax content etc..) + * + * @see {@link humhub.modules.content.handleAction} + * @param {Node|jQuery} parent - the event target + * @param {string} type - event type e.g. click, change,... + * @param {string} selector - jQuery selector + * @param {string} selector - jQuery selector + */ + module.bindAction = function (parent, type, selector, directHandler) { + parent = parent || document; + var $parent = parent.jquery ? parent : $(parent); + var actionEvent = type + '.humhub-action'; + + var actionBinding = new ActionBinding({ + parent: parent, + type: type, + event: actionEvent, + selector: selector, + directHandler: directHandler + }); + + // Add new ActionBinding with given settings. + actionBindings.push(actionBinding); + + $parent.on(actionEvent, selector, function (evt) { + evt.preventDefault(); + + // Get sure we don't call the handler twice if the event was already handled by trigger. + if ($(this).data('action-'+actionBinding.event) + || evt.originalEvent && evt.originalEvent.actionHandled) { + return; + } + + module.log.debug('Detected unhandled action', actionBinding); + updateBindings(); + actionBinding.handle(evt, $(this)); + }); + + return; + }; + + module.export({ + Component: Component + }); +}); \ No newline at end of file diff --git a/js/humhub/humhub.actions.js b/js/humhub/humhub.actions.js deleted file mode 100644 index f608118624..0000000000 --- a/js/humhub/humhub.actions.js +++ /dev/null @@ -1,274 +0,0 @@ -/** - * Thid module can be used by humhub sub modules for registering handlers and serves as core module for executing actions triggered in the gui. - * A module can either register global handler by using the registerHandler and registerAjaxHandler functions or use the content mechanism. - */ -humhub.initModule('actions', function (module, require, $) { - var _handler = {}; - var object = require('util').object; - var string = require('util').string; - var client = require('client'); - - var DATA_COMPONENT = 'action-component'; - var DATA_COMPONENT_SELECTOR = '[data-'+DATA_COMPONENT+']'; - - var Component = function(container) { - if(!container) { - return; - } - this.$ = (object.isString(container)) ? $('#' + container) : container; - this.base = this.$.data(DATA_COMPONENT); - }; - - Component.prototype.data = function(dataSuffix) { - var result = this.$.data(dataSuffix); - if(!result) { - var parentComponent = this.parent(); - if(parentComponent) { - return parentComponent.data(dataSuffix); - } - } - return result; - }; - - Component.prototype.parent = function() { - var $parent = this.$.parent().closest(DATA_COMPONENT_SELECTOR); - if($parent.length) { - try { - var ParentType = require($parent.data(DATA_COMPONENT)); - return new ParentType($parent); - } catch(err) { - console.error('Could not instantiate parent component: '+$parent.data(DATA_COMPONENT)); - } - } - }; - - Component.prototype.children = function() { - var result = []; - this.$.find(DATA_COMPONENT_SELECTOR).each(function() { - var component = Component.getInstance($(this)); - if(component) { - result.push(component); - } - }); - return result; - }; - - Component.prototype.hasAction = function(action) { - return this.actions().indexOf(action) >= 0; - }; - - Component.prototype.actions = function() { - return []; - }; - - Component.getInstance = function($node) { - //Determine closest component node (parent or or given node) - $node = (object.isString($node)) ? $('#'+$node) : $node; - var $componentRoot = ($node.data(DATA_COMPONENT)) ? $node : Component.getClosestComponentNode($node); - - var componentType = $componentRoot.data(DATA_COMPONENT); - - var ComponentType = require(componentType); - if(ComponentType) { - return new ComponentType($componentRoot); - } else { - console.error('Tried to instantiate component with invalid type: '+componentType); - } - }; - - Component.getClosestComponentNode = function($element) { - return $element.closest(DATA_COMPONENT_SELECTOR); - }; - - /** - * Handles the given componentAction event. The event should provide the following properties: - * - * $trigger (required) : the trigger node of the event - * handler (required) : the handler functionn name to be executed on the component - * type (optoinal) : the event type 'click', 'change',... - * - * @param {object} event - event object - * @returns {Boolean} true if the componentAction could be executed else false - */ - Component.handleAction = function(event) { - var component = Component.getInstance(event.$trigger); - if(component) { - //Check if the content instance provides this actionhandler - if(event.handler && component[event.handler]) { - component[event.handler](event); - return true; - } - } - return false; - }; - - /** - * Constructor for initializing the module. - */ - module.init = function () { - //Binding default action types - this.bindAction(document, 'click', '[data-action-click]'); - this.bindAction(document, 'dblclick', '[data-action-dblclick]'); - this.bindAction(document, 'change', '[data-action-mouseout]'); - - //Add addition for loader buttons - require('additions').registerAddition('[data-action-load-button]', function () { - var that = this; - this.on('click.humhub-action-load-button', function (evt) { - if (!that.find('.action-loader').length) { - that.append(''); - } - }); - }); - }; - - /** - * Registers a given handler with the given id. - * - * This handler will be called e.g. after clicking a button with the handler id as - * data-action-click attribute. - * - * The handler can access additional event information through the argument event. - * The this object within the handler will be the trigger of the event. - * - * @param {string} id handler id should contain the module namespace - * @param {function} handler function with one event argument - * @returns {undefined} - */ - module.registerHandler = function (id, handler) { - if (!id) { - return; - } - - if (handler) { - _handler[id] = handler; - } - }; - - /** - * Registers an ajax eventhandler. - * The function can either be called with four arguments (id, successhandler, errorhandler, additional config) - * or with two (id, cfg) where tha handlers are contained in the config object itself. - * - * The successhandler will be called only if the response does not contain any errors or errormessages. - * So the errorhandler is called for application and http errors. - * - * The config can contain additional ajax settings. - * - * @param {type} id - * @param {type} success - * @param {type} error - * @param {type} cfg - * @returns {undefined} - */ - module.registerAjaxHandler = function (id, success, error, cfg) { - cfg = cfg || {}; - if (!id) { - return; - } - - if (object.isFunction(success)) { - cfg.success = success; - cfg.error = error; - } else { - cfg = success; - } - - if (success) { - _handler[id] = function (event) { - var path = $(this).data('action-url-' + event.type) || $(this).data('action-url'); - client.ajax(path, cfg); - }; - } - }; - - /** - * Binds an delegate wrapper event handler to the parent node. This is used to detect action handlers like - * data-action-click events and map the call to either a stand alone handler or a content - * action handler. The trigger of a contentAction has to be contained in a data-content-base node. - * - * This function uses the jQuery event delegation: - * - * $(parent).on(type, selector, function(){...}); - * - * This assures the event binding for dynamic content (ajax content etc..) - * - * @see {@link humhub.modules.content.handleAction} - * @param {Node|jQuery} parent - the event target - * @param {string} type - event type e.g. click, change,... - * @param {string} selector - jQuery selector - * @param {string} selector - jQuery selector - */ - module.bindAction = function (parent, type, selector, directHandler) { - parent = parent || document; - var $parent = parent.jquery ? parent : $(parent); - $parent.on(type+'.humhub-action', selector, function (evt) { - evt.preventDefault(); - //The element which triggered the action e.g. a button or link - $trigger = $(this); - - //Get the handler id, either a stand alone handler or a content handler function e.g: 'edit' - var handlerId = $trigger.data('action' + '-' + type); - var event = {type: type, $trigger: $trigger, handler: handlerId}; - - event.finish = function() { - _removeLoaderFromEventTarget(evt); - }; - - //TODO: handle with $.Event - //var event = $.Event(type, {$trigger: $trigger}); - //event.originalEvent = evt; - - //Search and execute a stand alone handler or try to call the content action handler - try { - //Direct action handler - if(object.isFunction(directHandler)) { - directHandler.apply($trigger, [event]); - return; - } - - //Component handler - if(Component.handleAction(event)) { - return; - } - - //Registered handler - if(_handler[handlerId]) { - //Registered action handler - var handler = _handler[handlerId]; - handler.apply($trigger, [event]); - return; - } - - //As last resort we try to call the action by namespace handler - var splittedNS = handlerId.split('.'); - var handler = splittedNS[splittedNS.length - 1]; - var target = require(string.cutsuffix(handlerId, '.' + handler)); - if(object.isFunction(target)) { - target[handler]({type: type, $trigger: $trigger}); - } else { - console.error('Could not determine actionhandler for: '+handlerId); - } - } catch (e) { - //TODO: handle error ! - console.error('Error while handling action event for handler "' + handlerId+'"', e); - _removeLoaderFromEventTarget(evt); - } - }); - }; - - var _removeLoaderFromEventTarget = function (evt) { - if (evt.target) { - $target = $(evt.target); - $loader = $target.find('.action-loader'); - - if ($loader.length) { - $loader.remove(); - } - } - }; - - module.export({ - Component: Component - }); -}); \ No newline at end of file diff --git a/js/humhub/humhub.client.js b/js/humhub/humhub.client.js index c63da390a3..92938e89dc 100644 --- a/js/humhub/humhub.client.js +++ b/js/humhub/humhub.client.js @@ -5,32 +5,17 @@ humhub.initModule('client', function (module, require, $) { var object = require('util').object; - var init = function () { - /*$.ajaxPrefilter('html', function(options, originalOptions, jqXHR) { - debugger; - console.log(options); - var pjaxHandler = options.success; - options.success = function(result, textStatus, xhr) { - console.log(result); - pjaxHandler(result, textStatus, xhr); - }; - options.error = function(err) { - debugger; - }; - }); - - $.pjax.defaults.maxCacheLength = 0; - $('a.dashboard').on('click', function(evt) { - debugger; - evt.preventDefault(); - $.pjax({url:$(this).attr('href'), container: '#main-content', maxCacheLength:0, timeout:2000}); - });*/ - } /** * Response Wrapper Object for easily accessing common data */ - var Response = function (data) { - $.extend(this, data); + var Response = function (data, dataType) { + if(!dataType || dataType === 'json') { + $.extend(this, data); + } else if(dataType) { + this[dataType] = data; + } else { + this.data = data; + } }; /** @@ -88,7 +73,8 @@ humhub.initModule('client', function (module, require, $) { $form = object.isString($form) ? $($form) : $form; cfg.type = $form.attr('method') || 'post'; cfg.data = $form.serialize(); - ajax($form.attr('action'), cfg); + var url = cfg['url'] || $form.attr('action'); + return ajax(url, cfg); }; var post = function (path, cfg) { @@ -97,28 +83,39 @@ humhub.initModule('client', function (module, require, $) { cfg.method = 'POST'; return ajax(path, cfg); }; + + var get = function (path, cfg) { + var cfg = cfg || {}; + cfg.type = 'GET'; + cfg.method = 'GET'; + return ajax(path, cfg); + }; var ajax = function (path, cfg) { + if(object.isFunction(cfg)) { + cfg = {'success' : cfg}; + } + var promise = new Promise(function (resolve, reject) { cfg = cfg || {}; //Wrap the actual error handler with our own and call var errorHandler = cfg.error; - var error = function (xhr, textStatus, errorThrown, data, status) { + var error = function (xhr, textStatus, errorThrown, data) { //Textstatus = "timeout", "error", "abort", "parsererror", "application" if (errorHandler && object.isFunction(errorHandler)) { var response = new Response(); - response.setAjaxError(xhr, errorThrown, textStatus, data, status); + response.setAjaxError(xhr, errorThrown, textStatus, data, xhr.status); errorHandler(response); } - reject(xhr, textStatus, errorThrown, data, status); + reject({'textStatus': textStatus, 'response': xhr.responseJSON, 'error': errorThrown, 'data': data, 'status': xhr.status}); }; var successHandler = cfg.success; - var success = function (json, textStatus, xhr) { - var response = new Response(json); + var success = function (data, textStatus, xhr) { + var response = new Response(data, cfg.dataType); if (response.isError()) { //Application errors - return error(xhr, "application", response.getErrors(), json, response.getStatus()); + return error(xhr, "application", response.getErrors(), data, response.getStatus()); } else if (successHandler) { response.textStatus = textStatus; response.xhr = xhr; @@ -129,7 +126,7 @@ humhub.initModule('client', function (module, require, $) { promise.then(function() { // If content with tags are inserted in resolve, the ajaxComplete handler in yii.js - // makes shure redundant stylesheets are removed. + // makes sure redundant stylesheets are removed. Here we get sure it is called after inserting the response. $(document).trigger('ajaxComplete'); }); @@ -152,8 +149,8 @@ humhub.initModule('client', function (module, require, $) { module.export({ ajax: ajax, post: post, - submit: submit, - init: init + get: get, + submit: submit }); }); diff --git a/js/humhub/humhub.client.pjax.js b/js/humhub/humhub.client.pjax.js index 83da24ed3e..53bb3bd79d 100644 --- a/js/humhub/humhub.client.pjax.js +++ b/js/humhub/humhub.client.pjax.js @@ -1,10 +1,12 @@ humhub.initModule('client.pjax', function (module, require, $) { - var object = require('util').object; + var event = require('event'); + + module.initOnPjaxLoad = false; var init = function () { pjaxRedirectFix(); - installLoader(); - } + module.installLoader(); + }; var pjaxRedirectFix = function () { var pjaxXhr; @@ -18,8 +20,18 @@ humhub.initModule('client.pjax', function (module, require, $) { } }); + $(document).on("pjax:success", function (evt, data, status, xhr, options) { + event.trigger('humhub:modules:client:pjax:afterPageLoad', { + 'originalEvent': evt, + 'data': data, + 'status': status, + 'xhr': xhr, + 'options': options + }); + }); + $.ajaxPrefilter('html', function (options, originalOptions, jqXHR) { - orgErrorHandler = options.error; + var orgErrorHandler = options.error; options.error = function (xhr, textStatus, errorThrown) { var redirect = (xhr.status >= 301 && xhr.status <= 303) if (redirect && xhr.getResponseHeader('X-PJAX-REDIRECT-URL') != "") { @@ -31,19 +43,20 @@ humhub.initModule('client.pjax', function (module, require, $) { } } }); - } + }; var installLoader = function () { NProgress.configure({showSpinner: false}); NProgress.configure({template: '
'}); - jQuery(document).on('pjax:start', function () { + $(document).on('pjax:start', function () { NProgress.start(); }); - jQuery(document).on('pjax:end', function () { + + $(document).on('pjax:end', function () { NProgress.done(); }); - } + }; module.export({ init: init, diff --git a/js/humhub/humhub.core.js b/js/humhub/humhub.core.js index 0e623b9ff0..32b048af6a 100644 --- a/js/humhub/humhub.core.js +++ b/js/humhub/humhub.core.js @@ -13,6 +13,12 @@ var humhub = humhub || (function($) { */ var modules = {}; + /** + * Flat array with all registered modules. + * @type Array + */ + var moduleArr = []; + /** * Used to collect modules added while initial page load. * These modules will be intitialized after the document is ready. @@ -20,6 +26,13 @@ var humhub = humhub || (function($) { */ var initialModules = []; + /** + * Contains all modules which needs to be reinitialized after a pjax reload + * @type Array + */ + var pjaxInitModules = []; + + /** * Is set wehen document is ready * @type Boolean @@ -27,7 +40,8 @@ var humhub = humhub || (function($) { var initialized = false; /** - * Adds an module to the namespace. And initializes either after dom is ready. + * Adds a module to the namespace. And initializes after dom is ready. + * * The id can be provided either as * * - full namespace humhub.modules.ui.modal @@ -81,6 +95,14 @@ var humhub = humhub || (function($) { var instance = resolveNameSpace(id, true); instance.id = 'humhub.modules.'+_cutModulePrefix(id); instance.require = require; + instance.initOnPjaxLoad = true; + instance.config = require('config').module(instance); + instance.isModule = true; + + instance.text = function($key) { + return instance.config['text'][$key]; + }; + instance.export = function(exports) { $.extend(instance, exports); }; @@ -92,10 +114,17 @@ var humhub = humhub || (function($) { console.error('Error while creating module: '+id, err); } + moduleArr.push(instance); + + if(instance.init && instance.initOnPjaxLoad) { + pjaxInitModules.push(instance); + } + //Initialize the module when document is ready if(!initialized) { initialModules.push(instance); } else { + addModuleLogger(instance); instance.init(); } }; @@ -148,25 +177,29 @@ var humhub = humhub || (function($) { }); return result; } catch(e) { - //TODO: handle could not resolve type/namespace error - return; + var log = require('log') || console; + log.error('Error while resolving namespace: '+typePathe, e); } }; - /** + /** * Config implementation */ - - var config = { + var config = modules['config'] = { + id : 'config', + get : function(module, key, defaultVal) { if(arguments.length === 1) { - return this.getModuleConfig(module); + return this.module(module); } else if(_isDefined(key)) { - var result = this.getModuleConfig(module)[key]; + var result = this.module(module)[key]; return (_isDefined(result)) ? result : defaultVal; } }, - getModuleConfig: function(module) { + + module: function(module) { + module = (module.id) ? module.id : module; + module = _cutModulePrefix(module); if(!this[module]) { this[module] = {}; } @@ -185,13 +218,29 @@ var humhub = humhub || (function($) { that.set(moduleKey, config); }); }else if(arguments.length === 2) { - $.extend(this.getModuleConfig(moduleId), key); + $.extend(this.module(moduleId), key); } else if(arguments.length === 3) { - this.getModuleConfig(moduleId)[key] = value; + this.module(moduleId)[key] = value; } } }; + var event = modules['event'] = { + events : $({}), + on : function(event, selector, data, handler) { + this.events.on(event , selector, data, handler); + return this; + }, + trigger : function(eventType, extraParameters) { + this.events.trigger(eventType, extraParameters); + return this; + }, + one : function(event, selector, data, handler) { + this.events.one(event , selector, data, handler); + return this; + } + }; + /** * Cuts the prefix humub.modules or modules. from the given value. * @param {type} value @@ -233,18 +282,43 @@ var humhub = humhub || (function($) { return typeof obj !== 'undefined'; }; + var addModuleLogger = function(module, log) { + log = log || require('log'); + module.log = log.module(module); + } + //Initialize all initial modules $(document).ready(function() { + var log = require('log'); + + $.each(moduleArr, function(i, module) { + addModuleLogger(module, log); + }); + $.each(initialModules, function(i, module) { + event.trigger('humhub:beforeInitModule', module); if(module.init) { try { + event.trigger(module.id) module.init(); } catch(err) { - console.error('Could not initialize module: '+module.id, err); + log.error('Could not initialize module: '+module.id, err); } } - initialized = true; - console.log('Module initialized: '+module.id); + event.trigger('humhub:afterInitModule', module); + log.debug('Module initialized: '+module.id); + }); + + event.trigger('humhub:afterInit'); + + initialized = true; + }); + + event.on('humhub:modules:client:pjax:afterPageLoad', function (evt) { + $.each(pjaxInitModules, function(i, module) { + if(module.initOnPjaxLoad) { + module.init(); + } }); }); @@ -253,6 +327,7 @@ var humhub = humhub || (function($) { return { initModule: initModule, modules: modules, - config: config + config: config, + event: event, }; })($); \ No newline at end of file diff --git a/js/humhub/humhub.js b/js/humhub/humhub.js deleted file mode 100644 index 4a9da2ec2c..0000000000 --- a/js/humhub/humhub.js +++ /dev/null @@ -1,331 +0,0 @@ -var humhub = humhub || {}; - -humhub.util = (function(module, $) { - module.object = { - isFunction: function(obj) { - return Object.prototype.toString.call(obj) === '[object Function]'; - }, - - isObject: function(obj) { - return $.isPlainObject(obj); - }, - - isJQuery: function(obj) { - return obj.jquery; - }, - - isString: function(obj) { - return typeof obj === 'string'; - }, - - isNumber: function(n) { - return !isNaN(parseFloat(n)) && isFinite(n); - }, - - isBoolean: function(obj) { - return typeof obj === 'boolean'; - }, - - isDefined: function(obj) { - if(arguments.length > 1) { - var result = true; - var that = this; - this.each(arguments, function(index, value) { - if(!that.isDefined(value)) { - result = false; - return false; - } - }); - - return result; - } - return typeof obj !== 'undefined'; - } - }; - return module; -})(humhub.util || {}, $); - -humhub.modules = (function(module, $) { - var _handler = {}; - var _errorHandler = {}; - var object = humhub.util.object; - - var DATA_ACTION = 'action'; - - module.registerHandler = function(id, handler) { - if(!id) { - return; - } - - if(handler) { - _handler[id] = handler; - } - }; - - module.registerAjaxHandler = function(id, success, error, cfg) { - debugger; - cfg = cfg || {}; - if(!id) { - return; - } - - if(object.isFunction(success)) { - cfg.success = success; - cfg.error = error; - } else { - cfg = success; - } - - if(success) { - var that = this; - _handler[id] = function(event) { - var path = $(this).data('url-'+event.type) || $(this).data('url'); - that.ajax(path, cfg, event); - }; - } - - if(error) { - _errorHandler[id] = success; - } - }; - - module.bindAction = function(parent, type, selector) { - parent = parent || document; - var $parent = parent.jquery ? parent : $(parent); - $parent.on(type, selector, function(evt) { - evt.preventDefault(); - //The element which triggered the action e.g. a button or link - $trigger = $(this); - var handlerId = $trigger.data(DATA_ACTION+'-'+type); - var handler = _handler[handlerId]; - var event = {type:type, $trigger:$trigger}; - handler.apply($trigger, [event]); - }); - }; - - module.bindAction(document, 'click', '[data-action-click]'); - - /** - * Response Wrapper Object for - * easily accessing common data - */ - var Response = function(data) { - this.data = data; - }; - - Response.prototype.isConfirmation = function() { - return this.data && (this.data.status === 0); - }; - - Response.prototype.isError = function() { - return this.data && this.data.status && (this.data.status > 0); - }; - - Response.prototype.getErrors = function() { - return this.data.errors; - }; - - Response.prototype.getErrorCode = function() { - return this.data.errorCode; - }; - - Response.prototype.toString = function() { - return "{ status: "+this.data.status+" error: "+this.data.error+" data: "+this.data.data+" }"; - }; - - var errorHandler = function(cfg, xhr,type,errorThrown, errorCode, path) { - errorCode = (xhr) ? xhr.status : parseInt(errorCode); - console.warn("AjaxError: "+type+" "+errorThrown+" - "+errorCode); - - if(cfg.error && object.isFunction(cfg.error)) { - // "timeout", "error", "abort", "parsererror" or "application" - //TODO: den trigger als this verwenden - cfg.error(errorThrown, errorCode, type); - } else { - console.warn('Unhandled ajax error: '+path+" type"+type+" error: "+errorThrown); - } - }; - - module.ajax = function(path, cfg) { - var cfg = cfg || {}; - var async = cfg.async || true; - var dataType = cfg.dataType || "json"; - - var error = function(xhr,type,errorThrown, errorCode) { - errorHandler(cfg, xhr,type,errorThrown, errorCode, path); - }; - - var success = function(response) { - var responseWrapper = new Response(response); - - if(responseWrapper.isError()) { //Application errors - return error(undefined,"application",responseWrapper.getError(), responseWrapper.getErrorCode()); - } else if(cfg.success) { - cfg.success(responseWrapper); - } - }; - - $.ajax({ - url: path, - //crossDomain: true, //TODO: read from config - type : cfg.type, - processData : cfg.processData, - contentType: cfg.contentType, - async : async, - dataType: dataType, - success: success, - error: error - }); - }; - - return module; -})(humhub.modules || {}, $); - -humhub.modules.stream = (function(module, $) { - - var ENTRY_ID_SELECTOR_PREFIX = '#wallEntry_'; - var WALLSTREAM_ID = 'wallStream'; - - module.Entry = function(id) { - if(typeof id === 'string') { - this.id = id; - this.$ = $('#'+id); - } else if(id.jquery) { - this.$ = id; - this.id = this.$.attr('id'); - } - }; - - module.Entry.prototype.remove = function() { - this.$.remove(); - }; - - module.Entry.prototype.highlightContent = function() { - var $content = this.getContent(); - $content.addClass('highlight'); - $content.delay(200).animate({backgroundColor: 'transparent'}, 1000, function() { - $content.removeClass('highlight'); - $content.css('backgroundColor', ''); - }); - }; - - module.Entry.prototype.getContent = function() { - return this.$.find('.content'); - }; - - module.Stream = function(id) { - this.id = id; - this.$ = $('#'+id); - }; - - module.Stream.prototype.getEntry = function(id) { - return new module.Entry(this.$.find(ENTRY_ID_SELECTOR_PREFIX+id)); - }; - - module.Stream.prototype.wallStick = function(url) { - $.ajax({ - dataType: "json", - type: 'post', - url: url - }).done(function (data) { - if (data.success) { - if (currentStream) { - $.each(data.wallEntryIds, function (k, wallEntryId) { - currentStream.deleteEntry(wallEntryId); - currentStream.prependEntry(wallEntryId); - }); - $('html, body').animate({scrollTop: 0}, 'slow'); - } - } else { - alert(data.errorMessage); - } - }); - }; - - module.Stream.prototype.wallUnstick = function(url) { - $.ajax({ - dataType: "json", - type: 'post', - url: url - }).done(function (data) { - if (data.success) { - //Reload the whole stream, since we have to reorder the entries - currentStream.showStream(); - } - }); - }; - - /** - * Click Handler for Archive Link of Wall Posts - * (archiveLink.php) - * - * @param {type} className - * @param {type} id - */ - module.Stream.prototype.wallArchive = function(id) { - - url = wallArchiveLinkUrl.replace('-id-', id); - - $.ajax({ - dataType: "json", - type: 'post', - url: url - }).done(function (data) { - if (data.success) { - if (currentStream) { - $.each(data.wallEntryIds, function (k, wallEntryId) { - //currentStream.reloadWallEntry(wallEntryId); - // fade out post - setInterval(fadeOut(), 1000); - - function fadeOut() { - // fade out current archived post - $('#wallEntry_' + wallEntryId).fadeOut('slow'); - } - }); - } - } - }); - }; - - - /** - * Click Handler for Un Archive Link of Wall Posts - * (archiveLink.php) - * - * @param {type} className - * @param {type} id - */ - module.Stream.prototype.wallUnarchive = function(id) { - url = wallUnarchiveLinkUrl.replace('-id-', id); - - $.ajax({ - dataType: "json", - type: 'post', - url: url - }).done(function (data) { - if (data.success) { - if (currentStream) { - $.each(data.wallEntryIds, function (k, wallEntryId) { - currentStream.reloadWallEntry(wallEntryId); - }); - - } - } - }); - }; - - - module.getStream = function() { - if(!module.mainStream) { - module.mainStream = new module.Stream(WALLSTREAM_ID); - } - return module.mainStream; - }; - - module.getEntry = function(id) { - return module.getStream().getEntry(id); - }; - - return module; -})(humhub.core || {}, $); \ No newline at end of file diff --git a/js/humhub/humhub.log.js b/js/humhub/humhub.log.js new file mode 100644 index 0000000000..8a17e82912 --- /dev/null +++ b/js/humhub/humhub.log.js @@ -0,0 +1,230 @@ +/** + * + * @param {type} param1 + * @param {type} param2 + */ +humhub.initModule('log', function (module, require, $) { + + var event = require('event'); + + var TRACE_TRACE = 0; + var TRACE_DEBUG = 1; + var TRACE_INFO = 2; + var TRACE_SUCCESS = 3; + var TRACE_WARN = 4; + var TRACE_ERROR = 5; + var TRACE_FATAL = 6; + var TRACE_OFF = 7; + + var traceLevels = ['TRACE', 'DEBUG', 'INFO', 'SUCCESS', 'WARN', 'ERROR', 'FATAL', 'OFF']; + var config = require('config').module(module); + var object = require('util').object; + + var logger = {}; + + var Logger = function (module) { + if(object.isString(module)) { + this.module = require('module'); + this.moduleId = module; + } else if(module){ + this.module = module; + this.moduleId = module.id; + } + this.update(); + }; + + Logger.prototype.update = function () { + var result; + if (this.moduleId) { + var moduleConfig = require('config').module(this.moduleId); + if (moduleConfig.traceLevel && traceLevels.indexOf(moduleConfig.traceLevel.toUpperCase()) >= 0) { + result = traceLevels.indexOf(moduleConfig.traceLevel.toUpperCase()); + } + } + + if (!result) { + result = config.traceLevel || TRACE_INFO; + } + + return this.traceLevel = result; + }; + + Logger.prototype.trace = function (msg,details, setStatus) { + this._log(msg, details, setStatus, TRACE_TRACE); + }; + + Logger.prototype.debug = function (msg, details, setStatus) { + this._log(msg, details, setStatus, TRACE_DEBUG); + }; + + Logger.prototype.info = function (msg, details, setStatus) { + this._log(msg, details, setStatus, TRACE_INFO); + }; + + Logger.prototype.success = function (msg, setStatus) { + setStatus = object.isDefined(setStatus) ? setStatus : true; + this._log(msg, undefined, setStatus, TRACE_SUCCESS); + }; + + Logger.prototype.warn = function (msg, error, setStatus) { + this._log(msg, error, setStatus, TRACE_WARN); + }; + + Logger.prototype.error = function (msg, error, setStatus) { + this._log(msg, error, setStatus, TRACE_ERROR); + }; + + Logger.prototype.fatal = function (msg, error, setStatus) { + this._log(msg, error, setStatus, TRACE_FATAL); + }; + + Logger.prototype._log = function (msg, details, setStatus, level) { + try { + if (object.isBoolean(details)) { + setStatus = details; + details = undefined; + } + + if(msg instanceof Error && level >= TRACE_WARN) { + details = msg; + msg = this.getMessage(details.message, true); + } else if(object.isObject(msg) && msg.status && level >= TRACE_WARN) { + details = msg; + msg = this.getMessage(msg.status, true); + } else if(!object.isObject(msg)) { + msg = this.getMessage(msg, (!object.isDefined(msg) && level >= TRACE_WARN)); + } + + if (this.traceLevel > level) { + return; + } + + this._consoleLog(msg, level, details); + + if (setStatus) { + event.trigger('humhub:modules:log:setStatus', [msg, details, level]); + } + } catch(e) { + console.error('Error while generating log', e); + } + }; + + Logger.prototype.getMessage = function (key, returnDefault) { + if(!object.isString(key)) { + return key; + } + + var result; + + if(this.module) { + result = this.module.text(key); + } + + if(!result) { + result = module.text(key); + } + + if(!result && returnDefault) { + result = module.text('default.error'); + } else if(!result){ + result = key; + } + + return result; + }; + + Logger.prototype._consoleLog = function (msg, level, details) { + if (window.console) { + var consoleMsg = this.moduleId || 'root'; + consoleMsg += '(' + traceLevels[level] + '): ' + msg; + switch (level) { + case TRACE_ERROR: + case TRACE_FATAL: + console.error(consoleMsg, details); + break; + case TRACE_WARN: + if (details) { + console.warn(consoleMsg, details); + } else { + console.warn(consoleMsg); + } + break; + default: + if (details) { + console.log(consoleMsg, details); + } else { + console.log(consoleMsg); + } + break; + } + } + }; + + var init = function () { + module.rootLogger = new Logger(); + }; + + var getRootLogger = function () { + if (!module.rootLogger) { + module.rootLogger = new Logger(); + } + return module.rootLogger; + }; + + var getModuleLogger = function (module) { + var moduleId = (object.isString(module)) ? module : module.id; + if (!logger[moduleId]) { + logger[moduleId] = new Logger(module); + } + return logger[moduleId]; + }; + + var trace = function (msg, details, setStatus) { + module.getRootLogger().trace(msg, details, setStatus); + }; + + var debug = function (msg, details, setStatus) { + module.getRootLogger().debug(msg, details, setStatus); + }; + + var success = function (msg, details, setStatus) { + module.getRootLogger().success(msg, details, setStatus); + }; + + var info = function (msg, details, setStatus) { + module.getRootLogger().info(msg, details, setStatus); + }; + + var warn = function (msg, error, setStatus) { + module.getRootLogger().warn(msg, error, setStatus); + }; + + var error = function (msg, error, setStatus) { + module.getRootLogger().error(msg, error, setStatus); + }; + + var fatal = function (msg, error, setStatus) { + module.getRootLogger().fatal(msg, error, setStatus); + }; + + module.export({ + init: init, + Logger: Logger, + module: getModuleLogger, + getRootLogger: getRootLogger, + trace: trace, + debug: debug, + info: info, + success: success, + warn: warn, + error: error, + fata: fatal, + TRACE_TRACE: TRACE_TRACE, + TRACE_DEBUG: TRACE_DEBUG, + TRACE_INFO: TRACE_INFO, + TRACE_SUCCESS: TRACE_SUCCESS, + TRACE_WARN: TRACE_WARN, + TRACE_ERROR: TRACE_ERROR, + TRACE_OFF: TRACE_OFF + }); +}); \ No newline at end of file diff --git a/js/humhub/humhub.additions.js b/js/humhub/humhub.ui.additions.js similarity index 65% rename from js/humhub/humhub.additions.js rename to js/humhub/humhub.ui.additions.js index 07d7682ae2..0c089cdc29 100644 --- a/js/humhub/humhub.additions.js +++ b/js/humhub/humhub.ui.additions.js @@ -5,9 +5,12 @@ * An addition can be registered for a specific selector e.g: * It is possible to register multiple additions for the same selector. */ -humhub.initModule('additions', function(module, require, $) { +humhub.initModule('ui.additions', function (module, require, $) { + + var event = require('event'); + var _additions = {}; - + /** * Registers an addition for a given jQuery selector. There can be registered * multiple additions for the same selector. @@ -17,35 +20,47 @@ humhub.initModule('additions', function(module, require, $) { * @returns {undefined} */ module.registerAddition = function (selector, addition) { - if(!_additions[selector]) { + if (!_additions[selector]) { _additions[selector] = []; } - + _additions[selector].push(addition); }; - + /** * Applies all matched additions to the given element and its children * @param {type} element * @returns {undefined} */ - module.applyTo = function(element) { + module.applyTo = function (element) { var $element = $(element); - $.each(_additions, function(selector, additions) { - $.each(additions, function(i, addition) { - $.each($element.find(selector).addBack(selector), function() { + $.each(_additions, function (selector, additions) { + $.each(additions, function (i, addition) { + $.each($element.find(selector).addBack(selector), function () { try { var $match = $(this); addition.apply($match, [$match, $element]); - } catch(e) { - console.error('Error while applying addition '+addition+' on selector '+selector); + } catch (e) { + console.error('Error while applying addition on selector ' + selector, e); } }); }); }); }; - - module.init = function() { + + module.init = function () { + event.on('humhub:modules:client:pjax:afterPageLoad', function (evt, cfg) { + module.applyTo(cfg.options.container); + }); + + event.on('humhub:afterInit', function (evt) { + module.applyTo($('html')); + }); + + this.registerAddition('.autosize', function ($match) { + $match.autosize(); + }); + //TODO: apply to html on startup, the problem is this could crash legacy code. }; }); \ No newline at end of file diff --git a/js/humhub/humhub.ui.js b/js/humhub/humhub.ui.js deleted file mode 100644 index db51a9bc59..0000000000 --- a/js/humhub/humhub.ui.js +++ /dev/null @@ -1,8 +0,0 @@ -humhub.initModule('ui', function(module, require, $) { - var additions = require('additions'); - module.init = function() { - additions.registerAddition('.autosize', function($match) { - $match.autosize(); - }); - }; -}); \ No newline at end of file diff --git a/js/humhub/humhub.ui.loader.js b/js/humhub/humhub.ui.loader.js new file mode 100644 index 0000000000..75fa9fe02c --- /dev/null +++ b/js/humhub/humhub.ui.loader.js @@ -0,0 +1,166 @@ +/** + * Module for adding loader animations to dom nodes. + * + * The default loader animation can be added or appended/prepended as follows + * + * var loader = require('ui.loader'); + * + * // Overwrite current html content with loader animation + * loader.set(myNode); + * + * // Remove loader animation + * loader.reset(myNode); + * + * The loader module also adds an click handler to all buttons and links with a + * data-ui-loader attribute set. + * + * If a data-ui-loader button is used within a yii ActiveForm we automaticly reset all loader buttons + * in case of form validation errors. + * + * + * @param {type} param1 + * @param {type} param2 + */ +humhub.initModule('ui.loader', function (module, require, $) { + + var DEFAULT_LOADER_SELECTOR = '#humhub-ui-loader-default'; + + module.initOnPjaxLoad = false; + + var set = function (node, cfg) { + var $node = $(node); + if ($node.length) { + $node.each(function () { + var $this = $(this); + $this.data('htmlOld', $node.html()); + $this.html(getInstance(cfg)); + }); + + } + }; + + var append = function (node, cfg) { + var $node = $(node); + if ($node.length) { + $node.append(getInstance(cfg)); + } + }; + + var prepend = function (node, cfg) { + var $node = $(node); + if ($node.length) { + $node.prepend(getInstance(cfg)); + } + }; + + var reset = function (node) { + var $node = $(node); + var $loader = $node.find('.loader').length; + if (!$loader) { + return; + } + + $node.removeClass('disabled'); + + if ($loader && $node.data('htmlOld')) { + $node.html($node.data('htmlOld')); + } else if ($loader) { + $node.find('.loader').remove(); + } + }; + + var getInstance = function (cfg) { + cfg = cfg || {}; + + var $result = $(DEFAULT_LOADER_SELECTOR).clone().removeAttr('id').show(); + + if (cfg['cssClass']) { + $result.addClass(cfg['cssClass']); + } + + if (cfg['id']) { + $result.attr('id', cfg['id']); + } + + if (cfg['css']) { + $result.css(cfg['css']); + } + + if (cfg['position']) { + if (cfg['position'] === 'left') { + $result.find('.sk-spinner').css('margin', '0'); + } + $result.css(cfg['css']); + } + + if (cfg['size']) { + var size = cfg['size']; + $result.find('.sk-bounce1').css({'width': size, 'height': size}); + $result.find('.sk-bounce2').css({'width': size, 'height': size}); + $result.find('.sk-bounce3').css({'width': size, 'height': size}); + } + + return $result; + }; + + var init = function (cfg) { + $(document).on('click.humhub:modules:ui:loader', 'a[data-ui-loader], button[data-ui-loader]', function (evt) { + module.initLoaderButton(this, evt); + }); + + $(document).on('afterValidate.humhub:modules:ui:loader', function (evt, messages, errors) { + if (errors.length) { + $(evt.target).find('[data-ui-loader]').each(function () { + reset(this); + }); + } + }); + }; + + var initLoaderButton = function (node, evt) { + var $node = $(node); + var loader = $node.find('.loader').length > 0; + + /** + * Prevent multiple mouse clicks, if originalEvent is present its a real mouse event otherwise its script triggered + * This is a workaround since yii version 2.0.10 changed the activeForm submission from $form.submit() to data.submitObject.trigger("click"); + * which triggers this handler twice. Here we get sure not to block the script triggered submission. + */ + if (loader && evt.originalEvent) { + return false; + } else if (loader) { + return; + } + + // Adopt current color for the loader animation + var color = $node.css('color') || '#ffffff'; + var $loader = $(module.template); + + // Align bouncer animation color and size + $loader.find('.sk-bounce1, .sk-bounce2, .sk-bounce3') + .addClass('disabled') + .css({'background-color': color, 'width': '10px', 'height': '10px'}); + + // The loader does have some margin we have to hide + $node.css('overflow', 'hidden'); + $node.addClass('disabled'); + + // Prevent the container from resizing + $node.css('min-width', node.getBoundingClientRect().width); + $node.data('htmlOld', $node.html()); + $node.html($loader); + }; + + var template = ''; + + module.export({ + set: set, + append: append, + prepend: prepend, + reset: reset, + getInstance: getInstance, + template: template, + initLoaderButton: initLoaderButton, + init: init + }); +}); \ No newline at end of file diff --git a/js/humhub/humhub.ui.modal.js b/js/humhub/humhub.ui.modal.js index 2afc7815b6..22b17e7d4b 100644 --- a/js/humhub/humhub.ui.modal.js +++ b/js/humhub/humhub.ui.modal.js @@ -17,14 +17,26 @@ */ humhub.initModule('ui.modal', function (module, require, $) { var object = require('util').object; - var additions = require('additions'); - var config = humhub.config.getModuleConfig('ui.modal'); + var additions = require('ui.additions'); + var config = require('config').module(module); + + var loader = require('ui.loader'); + + module.initOnPjaxLoad = false; + //Keeps track of all initialized modals var modals = []; - var TMPL_MODAL_CONTAINER = ''; - var TMPL_MODAL_HEADER = ''; - var TMPL_MODAL_BODY = ''; + + /** + * Template for the modal splitted into different parts. Those can be overwritten my changing or overwriting module.template. + */ + var template = { + container : '', + header : '', + body: '', + } + var ERROR_DEFAULT_TITLE = 'Error'; var ERROR_DEFAULT_MESSAGE = 'An unknown error occured!'; @@ -50,7 +62,7 @@ humhub.initModule('ui.modal', function (module, require, $) { * @returns {undefined} */ Modal.prototype.createModal = function (id) { - this.$modal = $(TMPL_MODAL_CONTAINER).attr('id', id); + this.$modal = $(module.template.container).attr('id', id); $('body').append(this.$modal); }; @@ -97,7 +109,7 @@ humhub.initModule('ui.modal', function (module, require, $) { * @returns {undefined} */ Modal.prototype.reset = function () { - this.setBody('
'); + loader.set(this.$body); this.isFilled = false; }; @@ -241,7 +253,7 @@ humhub.initModule('ui.modal', function (module, require, $) { Modal.prototype.setTitle = function (title) { var $header = this.getHeader(); if (!$header.length) { - this.getContent().prepend($(TMPL_MODAL_HEADER)); + this.getContent().prepend($(module.template.header)); $header = this.getHeader(); } $header.find('.modal-title').html(title); @@ -255,7 +267,7 @@ humhub.initModule('ui.modal', function (module, require, $) { Modal.prototype.setBody = function (content) { var $body = this.getBody(); if (!$body.length) { - this.getContent().append($(TMPL_MODAL_BODY)); + this.getContent().append($(module.template.body)); $body = this.getBody(); } $body.html(content); @@ -277,13 +289,14 @@ humhub.initModule('ui.modal', function (module, require, $) { return this.$modal.find('.modal-body'); }; - var ConfirmModal = function(id, cfg) { + var ConfirmModal = function(id) { Modal.call(this, id); }; object.inherits(ConfirmModal, Modal); ConfirmModal.prototype.open = function(cfg) { + cfg = cfg || {}; this.clear(); cfg['header'] = cfg['header'] || config['defaultConfirmHeader']; cfg['body'] = cfg['body'] || config['defaultConfirmBody']; @@ -323,8 +336,6 @@ humhub.initModule('ui.modal', function (module, require, $) { cfg['cancel'](evt); }); } - - }; module.export({ @@ -335,6 +346,7 @@ humhub.initModule('ui.modal', function (module, require, $) { module.globalConfirm.open(cfg); }; }, - Modal: Modal + Modal: Modal, + template: template }); }); \ No newline at end of file diff --git a/js/humhub/humhub.ui.status.js b/js/humhub/humhub.ui.status.js new file mode 100644 index 0000000000..16c0e415aa --- /dev/null +++ b/js/humhub/humhub.ui.status.js @@ -0,0 +1,189 @@ +/** + * + * @param {type} param1 + * @param {type} param2 + */ +humhub.initModule('ui.status', function (module, require, $) { + + var event = require('event'); + var log = require('log'); + var object = require('util').object; + + var SELECTOR_ROOT = '#status-bar'; + var SELECTOR_BODY = '.status-bar-body'; + var SELECTOR_CONTENT = '.status-bar-content'; + + var AUTOCLOSE_DELAY = 6000; + + var StatusBar = function () { + this.$ = $(SELECTOR_ROOT); + }; + + StatusBar.prototype.info = function (msg, closeAfter) { + closeAfter = closeAfter || AUTOCLOSE_DELAY; + this._trigger('' + msg + '', undefined, closeAfter); + }; + + StatusBar.prototype.success = function (msg, closeAfter) { + closeAfter = closeAfter || AUTOCLOSE_DELAY; + this._trigger('' + msg + '', undefined, closeAfter); + }; + + StatusBar.prototype.warning = function (msg, error, closeAfter) { + this._trigger('' + msg + '', error, closeAfter); + }; + + StatusBar.prototype.error = function (msg, error, closeAfter) { + this._trigger('' + msg + '', error, closeAfter); + }; + + StatusBar.prototype._trigger = function (content, error, closeAfter) { + if (this.closeTimer) { + clearTimeout(this.closeTimer); + } + + var that = this; + this.hide(function () { + that.setContent(content, error).show(function () { + if (closeAfter > 0) { + that.closeTimer = setTimeout(function () { + that.hide(); + }, closeAfter); + } + }); + }); + }; + + StatusBar.prototype.setContent = function (content, error) { + var that = this; + var $content = this.$.find(SELECTOR_CONTENT).html(content); + var $closeButton = $('×'); + + if (error && module.config['showMore']) { + this._addShowMoreButton($content, error); + } + + $closeButton.on('click', function () { + that.hide(); + }); + + $content.prepend($closeButton); + return this; + }; + + StatusBar.prototype._addShowMoreButton = function ($content, error) { + var $showMore = $(''); + $showMore.on('click', function () { + var $details = $content.find('.status-bar-details'); + if($details.length) { + $details.stop().slideToggle('fast', function() { + $details.remove(); + }); + + $showMore.find('i').attr('class', 'fa fa-angle-up'); + } else { + $details = $('