From 829cf0e1b202f34a550c2ed4e4f808ba22a32f0c Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Fri, 2 Jul 2021 11:42:24 +0200 Subject: [PATCH] re-add accidently deleted home app.js but it will need a lot work: - gridster need to be legacy loaded (uses this instead of window) - calendar_home_portlet need to be loaded after this file --- home/js/app.js | 918 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 918 insertions(+) create mode 100644 home/js/app.js diff --git a/home/js/app.js b/home/js/app.js new file mode 100644 index 0000000000..fdcd9fb83b --- /dev/null +++ b/home/js/app.js @@ -0,0 +1,918 @@ +/** + * EGroupware - Home - Javascript UI + * + * @link http://www.egroupware.org + * @package home + * @author Nathan Gray + * @copyright (c) 2013 Nathan Gray + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + */ + +/*egw:uses + /vendor/bower-asset/jquery/dist/jquery.js; + /vendor/bower-asset/jquery-ui/jquery-ui.js; + /vendor/npm-asset/gridster/dist/jquery.gridster.js; +*/ +import {AppJS} from "../../api/js/jsapi/app_base.js"; +import {et2_createWidget} from "../../api/js/etemplate/et2_core_widget"; +import {et2_dialog} from "../../api/js/etemplate/et2_widget_dialog"; +import {et2_button} from "../../api/js/etemplate/et2_widget_button"; +// need legacy loading (uses this instead of window): import "../../vendor/npm-asset/gridster/dist/jquery.gridster.js"; +import "../../api/js/jsapi/egw_inheritance.js"; // Class + +/** + * JS for home application + * + * Home is a collection of little bits of content (portlets) from the other applications. + * + * Uses Gridster for the grid layout + * @see http://gridster.net + * @augments AppJS + */ +app.classes.home = (function(){ "use strict"; return AppJS.extend( +{ + /** + * AppJS requires overwriting this with the actual application name + */ + appname: "home", + + /** + * Grid resolution. Must match et2_portlet GRID + */ + GRID: 50, + + /** + * Default size for new portlets + */ + DEFAULT: { + WIDTH: 4, + HEIGHT: 1 + }, + + // List of portlets + portlets: {}, + + /** + * Constructor + * + * @memberOf app.home + */ + init: function() + { + // call parent + this._super.apply(this, arguments); + }, + + /** + * Destructor + * @memberOf app.home + */ + destroy: function() + { + delete this.et2; + delete this.portlet_container; + + this.portlets = {}; + + // call parent + this._super.apply(this, arguments); + + // Make sure all other sub-etemplates in portlets are done + var others = etemplate2.getByApplication(this.appname); + for(var i = 0; i < others.length; i++) + { + others[i].clear(); + } + }, + + /** + * This function is called when the etemplate2 object is loaded + * and ready. If you must store a reference to the et2 object, + * make sure to clean it up in destroy(). + * + * @param {etemplate2} et2 Newly ready object + * @param {string} Template name + */ + et2_ready: function(et2, name) + { + // Top level + if(name == 'home.index') + { + // call parent + this._super.apply(this, arguments); + + this.et2.set_id('home.index'); + this.et2.set_actions(this.et2.getArrayMgr('modifications').getEntry('home.index')['actions']); + + this.portlet_container = this.et2.getWidgetById("portlets"); + + // Set up sorting of portlets + this._do_ordering(); + + // Accept drops of favorites, which aren't part of action system + jQuery(this.et2.getDOMNode().parentNode).droppable({ + hoverClass: 'drop-hover', + accept: function(draggable) { + // Check for direct support for that application + if(draggable[0].dataset && draggable[0].dataset.appname) + { + return egw_getActionManager('home',false,1).getActionById('drop_'+draggable[0].dataset.appname +'_favorite_portlet') != null; + } + return false; + }, + drop: function(event, ui) { + // Favorite dropped on home - fake an action and divert to normal handler + var action = { + data: { + class: 'add_home_favorite_portlet' + } + } + + // Check for direct support for that application + if(ui.helper.context.dataset && ui.helper.context.dataset.appname) + { + var action = egw_getActionManager('home',false,1).getActionById('drop_'+ui.helper.context.dataset.appname +'_favorite_portlet') || {} + } + action.ui = ui; + app.home.add_from_drop(action, [{data: ui.helper.context.dataset}]) + } + }) + // Bind to unload to remove it from our list + .on('clear','.et2_container[id]', jQuery.proxy(function(e) { + if(e.target && e.target.id && this.portlets[e.target.id]) + { + this.portlets[e.target.id].destroy(); + delete this.portlets[e.target.id]; + } + },this)); + } + else if (et2.uniqueId) + { + let portlet_container = this.portlet_container || window.app.home?.portlet_container; + // Handle bad timing - a sub-template was finished first + if(!portlet_container) + { + window.setTimeout(jQuery.proxy(function() {this.et2_ready(et2, name);},this),200); + return; + } + + var portlet = portlet_container.getWidgetById(et2.uniqueId); + // Check for existing etemplate, this one loaded over it + // NOTE: Moving them around like this can cause problems with event handlers + var existing = etemplate2.getById(et2.uniqueId); + if(portlet && existing) + { + for(var i = 0; i < portlet._children.length; i++) + { + if(typeof portlet._children[i]._init == 'undefined') + { + portlet.removeChild(portlet._children[i]) + } + } + } + // It's in the right place for original load, but move it into portlet + var misplaced = jQuery(etemplate2.getById('home-index').DOMContainer).siblings('#'+et2.DOMContainer.id); + if(portlet) + { + portlet.content = jQuery(et2.DOMContainer).appendTo(portlet.content); + portlet.addChild(et2.widgetContainer); + et2.resize(); + } + if(portlet && misplaced.length) + { + et2.DOMContainer.id = et2.uniqueId; + } + + // Instanciate custom code for this portlet + this._get_portlet_code(portlet); + } + + // Special handling to deal with legacy (non-et2) calendar links + if(name == 'home.legacy') + { + jQuery('.calendar_calDayColHeader a, .calendar_plannerDayScale a, .calendar_plannerWeekScale a, .calendar_plannerMonthScale a, .calendar_calGridHeader a', et2.DOMContainer) + .on('click', function(e) { + egw.link_handler(this.href,'calendar'); + return false; + }); + } + }, + + /** + * Observer method receives update notifications from all applications + * + * Home passes the notification off to specific code for each portlet, which + * decide if they should be updated or not. + * + * @param {string} _msg message (already translated) to show, eg. 'Entry deleted' + * @param {string} _app application name + * @param {(string|number)} _id id of entry to refresh or null + * @param {string} _type either 'update', 'edit', 'delete', 'add' or null + * - update: request just modified data from given rows. Sorting is not considered, + * so if the sort field is changed, the row will not be moved. + * - edit: rows changed, but sorting may be affected. Requires full reload. + * - delete: just delete the given rows clientside (no server interaction neccessary) + * - add: requires full reload for proper sorting + * @param {string} _msg_type 'error', 'warning' or 'success' (default) + * @param {string} _targetapp which app's window should be refreshed, default current + * @return {false|*} false to stop regular refresh, thought all observers are run + */ + observer: function(_msg, _app, _id, _type, _msg_type, _targetapp) + { + for(var id in this.portlets) + { + // App is home, refresh all portlets + if(_app == 'home') + { + this.refresh(id); + continue; + } + + // Ask the portlets if they're interested + try + { + var code = this.portlets[id]; + if(code) + { + code.observer(_msg,_app,_id,_type,_msg_type,_targetapp); + } + } + catch(e) + { + this.egw.debug("error", "Error trying to update portlet " + id,e); + } + } + return false; + }, + + /** + * Add a new portlet from the context menu + */ + add: function(action, source) { + // Basic portlet attributes + var attrs = { + id: this._create_id(), + class: action.data.class, + width: this.DEFAULT.WIDTH, + height: this.DEFAULT.HEIGHT + }; + + // Try to put it about where the menu was opened + if(action.menu_context) + { + var $portlet_container = jQuery(this.portlet_container.getDOMNode()); + attrs.row = Math.max(1,Math.round((action.menu_context.posy - $portlet_container.offset().top )/ this.GRID)+1); + attrs.col = Math.max(1,Math.round((action.menu_context.posx - $portlet_container.offset().left) / this.GRID)+1); + } + + // Don't pass default width & height so class can set it + delete attrs.width; + delete attrs.height; + var portlet = et2_createWidget('portlet',jQuery.extend({},attrs), this.portlet_container); + portlet.loadingFinished(); + + // Immediately add content ID so etemplate loads into the right place + portlet.content.append('
'); + + // Get actual attributes & settings, since they're not available client side yet + portlet._process_edit(et2_dialog.OK_BUTTON, attrs); + + // Set up sorting/grid of new portlet + var $portlet_container = jQuery(this.portlet_container.getDOMNode()); + $portlet_container.data("gridster").add_widget( + portlet.getDOMNode(), + this.DEFAULT.WIDTH, this.DEFAULT.HEIGHT, + attrs.col, attrs.row + ); + + // Instanciate custom code for this portlet + this._get_portlet_code(portlet); + }, + + /** + * User dropped something on home. Add a new portlet + */ + add_from_drop: function(action,source) { + + // Actions got confused drop vs popup + if(source[0].id == 'portlets') + { + return this.add(action); + } + + var $portlet_container = jQuery(this.portlet_container.getDOMNode()); + + // Basic portlet attributes + var attrs = { + id: this._create_id(), + class: action.data.class || action.id.substr(5), + width: this.DEFAULT.WIDTH, + height: this.DEFAULT.HEIGHT + }; + + // Try to find where the drop was + if(action != null && action.ui && action.ui.position) + { + attrs.row = Math.max(1,Math.round((action.ui.position.top - $portlet_container.offset().top )/ this.GRID)); + attrs.col = Math.max(1,Math.round((action.ui.position.left - $portlet_container.offset().left) / this.GRID)); + } + + var portlet = et2_createWidget('portlet',jQuery.extend({},attrs), this.portlet_container); + portlet.loadingFinished(); + // Immediately add content ID so etemplate loads into the right place + portlet.content.append('
'); + + // Get actual attributes & settings, since they're not available client side yet + var drop_data = []; + for(var i = 0; i < source.length; i++) + { + if(source[i].id) + { + drop_data.push(source[i].id); + } + else + { + drop_data.push(source[i].data); + } + } + // Don't pass default width & height so class can set it + delete attrs.width; + delete attrs.height; + portlet._process_edit(et2_dialog.OK_BUTTON, jQuery.extend({dropped_data: drop_data},attrs)); + + // Set up sorting/grid of new portlet + $portlet_container.data("gridster").add_widget( + portlet.getDOMNode(), + this.DEFAULT.WIDTH, this.DEFAULT.HEIGHT, + attrs.col, attrs.row + ); + + // Instanciate custom code for this portlet + this._get_portlet_code(portlet); + }, + + /** + * Set the current selection as default for other users + * + * Only works (and available) for admins, this shows a dialog to select + * the group, and then sets the default for that group. + * + * @param {egwAction} action + * @param {egwActionObject[]} selected + */ + set_default: function(action, selected) { + // Gather just IDs, server will handle the details + var portlet_ids = []; + var group = action.data.portlet_group || false; + if(selected[0].id == 'home.index') + { + // Set all + this.portlet_container.iterateOver(function(portlet) { + portlet_ids.push(portlet.id); + },this,et2_portlet); + } + else + { + for(var i = 0; i < selected.length; i++) + { + portlet_ids.push(selected[i].id); + + // Read the associated group so we can properly remove it + var portlet = egw.preference(selected[i].id,'home'); + if(!group && portlet && portlet.group) + { + group = portlet.group; + } + } + } + + if(action.id.indexOf("remove_default") == 0) + { + // Disable action for feedback + action.set_enabled(false); + + // Pass them to server + egw.json('home_ui::ajax_set_default', ['delete', portlet_ids, group]).sendRequest(true); + return; + } + var dialog = et2_createWidget("dialog",{ + // If you use a template, the second parameter will be the value of the template, as if it were submitted. + callback: function(button_id, value) { + if(button_id != et2_dialog.OK_BUTTON) return; + + // Pass them to server + egw.json('home_ui::ajax_set_default', ['add', portlet_ids, value.group||false]).sendRequest(true); + }, + buttons: et2_dialog.BUTTONS_OK_CANCEL, + title: action.caption, + template:"home.set_default", + value: {content:{}, sel_options: {group:{default: egw.lang('All'), forced: egw.lang('Forced')}}} + }); + }, + + /** + * Allow a refresh from anywhere by triggering an update with no changes + * + * @param {string} id + */ + refresh: function(id) { + var p = this.portlet_container.getWidgetById(id); + if(p) + { + p._process_edit(et2_dialog.OK_BUTTON, '~reload~'); + } + }, + + /** + * Determine the best fitting code to use for the given portlet, instanciate + * it and add it to the list. + * + * @param {et2_portlet} portlet + * @returns {home_portlet} + */ + _get_portlet_code: function(portlet) { + var classname = portlet.class; + // Freshly added portlets can have 'add_' prefix + if(classname.indexOf('add_') == 0) + { + classname = classname.replace('add_',''); + } + // Prefer a specific match + var _class = app.classes.home[classname] || + // If it has a nextmatch, use favorite base class + (portlet.getWidgetById('nm') ? app.classes.home.home_favorite_portlet : false) || + // Fall back to base class + app.classes.home.home_portlet; + + this.portlets[portlet.id] = new _class(portlet); + + return this.portlets[portlet.id]; + }, + + /** + * For link_portlet - opens the configured record when the user + * double-clicks or chooses view from the context menu + */ + open_link: function(action) { + + // Get widget + var widget = null; + while(action.parent != null) + { + if(action.data && action.data.widget) + { + widget = action.data.widget; + break; + } + action = action.parent; + } + if(widget == null) + { + egw().log("warning", "Could not find widget"); + return; + } + egw().open(widget.options.settings.entry, "", 'view',null,widget.options.settings.entry.app); + }, + + /** + * Set up the drag / drop / re-order of portlets + */ + _do_ordering: function() { + var $portlet_container = jQuery(this.portlet_container.getDOMNode()); + $portlet_container + .addClass("home ui-helper-clearfix") + .disableSelection() + /* Gridster */ + .gridster({ + widget_selector: 'div.et2_portlet', + // Dimensions + margins = grid spacing + widget_base_dimensions: [this.GRID-5, this.GRID-5], + widget_margins: [5,5], + extra_rows: 1, + extra_cols: 1, + min_cols: 3, + min_rows: 3, + /** + * Set which parameters we want when calling serialize(). + * @param $w jQuery jQuery-wrapped element + * @param grid Object Grid settings + * @return Object - will be returned by gridster.serialize() + */ + serialize_params: function($w, grid) { + return { + id: $w.attr('id').replace(app.home.portlet_container.getInstanceManager().uniqueId+'_',''), + row: grid.row, + col: grid.col, + width: grid.size_x, + height: grid.size_y + }; + }, + /** + * Gridster's internal drag settings + */ + draggable: { + handle: '.ui-widget-header', + stop: function(event,ui) { + // Update widget(s) + var changed = this.serialize_changed(); + + // Reset changed, or they keep accumulating + this.$changed = jQuery([]); + + for (var key in changed) + { + if(!changed[key].id) continue; + // Changed ID is the ID + var widget = window.app.home.portlet_container.getWidgetById(changed[key].id); + if(!widget || widget == window.app.home.portlet_container) continue; + + egw().jsonq("home.home_ui.ajax_set_properties",[changed[key].id, {},{ + row: changed[key].row, + col: changed[key].col + },widget.settings?widget.settings.group:false], + null, + widget, true, widget + ); + } + } + } + + }); + + // Rescue selectboxes from Firefox + $portlet_container.on('mousedown touchstart', 'select', function(e) { + e.stopPropagation(); + }); + // Bind window resize to re-layout gridster + jQuery(window).one("resize."+this.et2._inst.uniqueId, function() { + // Note this doesn't change the positions, just makes them invalid + $portlet_container.data('gridster').recalculate_faux_grid(); + }); + // Bind resize to update gridster - this may happen _before_ the widget gets a + // chance to update itself, so we can't use the widget + $portlet_container + .on("resizestop", function(event, ui) { + $portlet_container.data("gridster").resize_widget( + ui.element, + Math.round(ui.size.width / app.home.GRID), + Math.round(ui.size.height / app.home.GRID) + ); + }); + }, + + /** + * Create an ID that should be unique, at least amoung a single user's portlets + */ + _create_id: function() { + var id = ''; + do + { + id = Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + } + while(this.portlet_container.getWidgetById('portlet_'+id)); + return 'portlet_'+id; + }, + + /** + * Functions for the list portlet + */ + /** + * For list_portlet - opens a dialog to add a new entry to the list + * + * @param {egwAction} action Drop or add action + * @param {egwActionObject[]} Selected entries + * @param {egwActionObject} target_action Drop target + */ + add_link: function(action, source, target_action) { + // Actions got confused drop vs popup + if(source[0].id == 'portlets') + { + return this.add_link(action); + } + + // Get widget + var widget = null; + while(action.parent != null) + { + if(action.data && action.data.widget) + { + widget = action.data.widget; + break; + } + action = action.parent; + } + if(target_action == null) + { + // use template base url from initial template, to continue using webdav, if that was loaded via webdav + var splitted = 'home.edit'.split('.'); + var path = app.home.portlet_container.getRoot()._inst.template_base_url + splitted.shift() + "/templates/default/" + + splitted.join('.')+ ".xet"; + var dialog = et2_createWidget("dialog",{ + callback: function(button_id, value) { + if(button_id == et2_dialog.CANCEL_BUTTON) return; + var new_list = widget.options.settings.list || []; + for(var i = 0; i < new_list.length; i++) + { + if(new_list[i].app == value.add.app && new_list[i].id == value.add.id) + { + // Duplicate - skip it + return; + } + } + value.add.link_id = value.add.app + ':' + value.add.id; + // Update server side + new_list.push(value.add); + widget._process_edit(button_id,{list: new_list}); + // Update client side + var list = widget.getWidgetById('list'); + if(list) + { + list.set_value(new_list); + } + }, + buttons: et2_dialog.BUTTONS_OK_CANCEL, + title: app.home.egw.lang('add'), + template:path, + value: { content: [{label: app.home.egw.lang('add'),type: 'link-entry',name: 'add',size:''}]} + }); + } + else + { + // Drag'n'dropped something on the list - just send action IDs + var new_list = widget.options.settings.list || []; + var changed = false; + for(var i = 0; i < new_list.length; i++) + { + // Avoid duplicates + for(var j = 0; j < source.length; j++) + { + if(!source[j].id || new_list[i].app+"::"+new_list[i].id == source[j].id) + { + // Duplicate - skip it + source.splice(j,1); + } + } + } + for(var i = 0; i < source.length; i++) + { + var explode = source[i].id.split('::'); + new_list.push({app: explode[0],id: explode[1], link_id: explode.join(':')}); + changed = true; + } + if(changed) + { + widget._process_edit(et2_dialog.OK_BUTTON,{ + list: new_list || {} + }); + } + // Filemanager support - links need app = 'file' and type set + for(var i = 0; i < new_list.length; i++) + { + if(new_list[i]['app'] == 'filemanager') + { + new_list[i]['app'] = 'file'; + new_list[i]['path'] = new_list[i]['title'] = new_list[i]['icon'] = new_list[i]['id']; + } + } + + widget.getWidgetById('list').set_value(new_list); + } + }, + + /** + * Remove a link from the list + */ + link_change: function(list, link_id, row) { + // Quick response client side + row.slideUp(row.remove); + + // Actual removal + var portlet = list._parent._parent; + portlet.options.settings.list.splice(row.index(), 1); + portlet._process_edit(et2_dialog.OK_BUTTON,{list: portlet.options.settings.list || {}}); + }, + + /** + * Functions for the note portlet + */ + /** + * Set up for editing a note + * CKEditor has CSP issues, so we need a popup + * + * @param {egwAction} action + * @param {egwActionObject[]} Selected + */ + note_edit: function(action, selected) { + if(!selected && typeof action == 'string') + { + var id = action; + } + else + { + var id = selected[0].id; + } + + // Aim to match the size + var portlet_dom = jQuery('[id$='+id+'][data-sizex]',this.portlet_container.getDOMNode()); + var width = portlet_dom.attr('data-sizex') * this.GRID; + var height = portlet_dom.attr('data-sizey') * this.GRID; + + // CKEditor is impossible to use below a certain size + // Add 35px for the toolbar, 35px for the buttons + var window_width = Math.max(580, width+20); + var window_height = Math.max(350, height+70); + + // Open popup, but add 70 to the height for the toolbar + egw.open_link(egw.link('/index.php',{ + menuaction: 'home.home_note_portlet.edit', + id: id, + height: window_height - 70 + }),'home_'+id, window_width+'x'+window_height,'home'); + }, + + /** + * Favorites / nextmatch + */ + /** + * Toggle the nextmatch header shown / hidden + * + * @param {Event} event + * @param {et2_button} widget + */ + nextmatch_toggle_header: function(event, widget) { + widget.set_class(widget.class == 'opened' ? 'closed' : 'opened'); + // We operate on the DOM here, nm should be unaware of our fiddling + var nm = widget.getParent().getWidgetById('nm'); + if(!nm) return; + + // Hide header + nm.div.toggleClass('header_hidden'); + nm.set_hide_header(nm.div.hasClass('header_hidden')); + nm.resize(); + } +})}).call(window); + +/// Base class code + +/** + * Base class for portlet specific javascript + * + * Should this maybe extend et2_portlet? It would complicate instantiation. + * + * @type @exp;Class@call;extend + */ +app.classes.home.home_portlet = Class.extend({ + portlet: null, + + init: function(portlet) { + this.portlet = portlet; + }, + destroy: function() { + this.portlet = null; + }, + + /** + * Handle framework refresh messages to determine if the portlet needs to + * refresh too. + * + * App is responsible for only reacting to "messages" it is interested in! + * + */ + observer: function(_msg, _app, _id, _type, _msg_type, _targetapp) + { + // Not interested + } +}); + +app.classes.home.home_link_portlet = app.classes.home.home_portlet.extend({ + init: function(portlet) { + // call parent + this._super.apply(this, arguments); + + // Check for tooltip + if(this.portlet) + { + var content = jQuery('.tooltip',this.portlet.content); + if(content.length && content.children().length) + { + //Check if the tooltip is already initialized + this.portlet.content.tooltip({ + items: this.portlet.content, + content: content.html(), + tooltipClass: 'portlet_' + this.portlet.id, + show: {effect: 'slideDown', delay:500}, + hide: {effect: 'slideUp', delay: 500}, + position: {my: "left top", at:"left bottom", collision: "flipfit"}, + open: jQuery.proxy(function(event, ui) { + // Calendar specific formatting + if(ui.tooltip.has('.calendar_calEventTooltip').length) + { + ui.tooltip.removeClass("ui-tooltip"); + ui.tooltip.addClass("calendar_uitooltip"); + } + },this), + close: function(event,ui) { + ui.tooltip.hover( + function() { + jQuery(this).stop(true).fadeTo(100,1); + }, + function() { + jQuery(this).slideUp("400",function() {jQuery(this).remove();}); + } + ); + } + }); + } + } + }, + observer: function(_msg, _app, _id, _type) + { + if(this.portlet && this.portlet.settings) + { + var value = this.portlet.settings.entry || {}; + if(value.app && value.app == _app && value.id && value.id == _id) + { + // We don't just get the updated title, in case there's a custom + // template with more fields + app.home.refresh(this.portlet.id); + } + } + } +}); +app.classes.home.home_list_portlet = app.classes.home.home_portlet.extend({ + observer: function(_msg, _app, _id, _type) + { + if(this.portlet && this.portlet.getWidgetById('list')) + { + var list = this.portlet.getWidgetById('list').options.value; + for(var i = 0; i < list.length; i++) + { + if(list[i].app == _app && list[i].id == _id) + { + app.home.refresh(this.portlet.id); + return; + } + } + } + } +}); +app.classes.home.home_weather_portlet = app.classes.home.home_portlet.extend({ + init: function(portlet) { + // call parent + this._super.apply(this, arguments); + + // Use location API + if(!this.portlet.options.settings && 'geolocation' in navigator) + { + navigator.geolocation.getCurrentPosition(function(position) { + if(portlet && portlet.options && portlet.options.settings && + portlet.options.settings.position && portlet.options.settings.position == position.coords.latitude + ',' + position.coords.longitude) + { + return; + } + portlet._process_edit(et2_dialog.OK_BUTTON, {position: position.coords.latitude + ',' + position.coords.longitude}); + }); + } + } +}); +app.classes.home.home_favorite_portlet = app.classes.home.home_portlet.extend({ + init: function(portlet) { + // call parent + this._super.apply(this, arguments); + + // Somehow favorite got lost, or is not set + if(portlet.options && portlet.options.settings && typeof portlet.options.settings !== 'undefined' && + !portlet.options.settings.favorite + ) + { + portlet.edit_settings(); + } + }, + observer: function(_msg, _app, _id, _type, _msg_type, _targetapp) + { + if(this.portlet.class.indexOf(_app) == 0 || this.portlet.class == 'home_favorite_portlet') + { + this.portlet.getWidgetById('nm').refresh(_id,_type); + } + } +}); + + +/** + * An example illustrating extending the base code for a application specific code. + * See also the calendar app, which needs custom handlers + * + * @type @exp;app@pro;classes@pro;home@pro;home_favorite_portlet@call;extend + * Note we put it in home, but this code should go in addressbook/js/addressbook_favorite_portlet.js + * +app.classes.home.addressbook_favorite_portlet = app.classes.home.home_favorite_portlet.extend({ + +observer: function(_msg, _app, _id, _type, _msg_type, _targetapp) +{ + // Just checking... + debugger; +} +}); +*/