Commit | Line | Data |
---|---|---|
dd3770bc CW |
1 | // https://civicrm.org/licensing |
2 | /* global CRM, ts */ | |
4e1046df | 3 | /*jshint loopfunc: true */ |
ce2cc43e | 4 | (function($) { |
dd3770bc | 5 | 'use strict'; |
3d80d622 CW |
6 | // Constructor for dashboard object. |
7 | $.fn.dashboard = function(options) { | |
8 | // Public properties of dashboard. | |
9 | var dashboard = {}; | |
10 | dashboard.element = this.empty(); | |
11 | dashboard.ready = false; | |
dd3770bc | 12 | dashboard.columns = []; |
4e1046df | 13 | dashboard.widgets = {}; |
3d80d622 CW |
14 | |
15 | /** | |
16 | * Public methods of dashboard. | |
17 | */ | |
18 | ||
19 | // Saves the order of widgets for all columns including the widget.minimized status to options.ajaxCallbacks.saveColumns. | |
ce2cc43e | 20 | dashboard.saveColumns = function(showStatus) { |
3d80d622 | 21 | // Update the display status of the empty placeholders. |
4e1046df CW |
22 | $.each(dashboard.columns, function(c, col) { |
23 | if ( typeof col == 'object' ) { | |
24 | // Are there any visible children of the column (excluding the empty placeholder)? | |
25 | if (col.element.children(':visible').not(col.emptyPlaceholder).length > 0) { | |
26 | col.emptyPlaceholder.hide(); | |
27 | } | |
28 | else { | |
29 | col.emptyPlaceholder.show(); | |
30 | } | |
31 | } | |
32 | }); | |
3d80d622 CW |
33 | |
34 | // Don't save any changes to the server unless the dashboard has finished initiating. | |
35 | if (!dashboard.ready) { | |
36 | return; | |
37 | } | |
38 | ||
39 | // Build a list of params to post to the server. | |
40 | var params = {}; | |
41 | ||
42 | // For each column... | |
4e1046df | 43 | $.each(dashboard.columns, function(c, col) { |
3d80d622 CW |
44 | |
45 | // IDs of the sortable elements in this column. | |
4e1046df | 46 | var ids = (typeof col == 'object') ? col.element.sortable('toArray') : []; |
3d80d622 CW |
47 | |
48 | // For each id... | |
4e1046df CW |
49 | $.each(ids, function(w, id) { |
50 | if (typeof id == 'string') { | |
51 | // Chop 'widget-' off of the front so that we have the real widget id. | |
52 | id = id.substring('widget-'.length); | |
53 | // Add one flat property to the params object that will look like an array element to the PHP server. | |
54 | // Unfortunately jQuery doesn't do this for us. | |
55 | if (typeof dashboard.widgets[id] == 'object') params['columns[' + c + '][' + id + ']'] = (dashboard.widgets[id].minimized ? '1' : '0'); | |
56 | } | |
57 | }); | |
58 | }); | |
3d80d622 CW |
59 | |
60 | // The ajaxCallback settings overwrite any duplicate properties. | |
61 | $.extend(params, opts.ajaxCallbacks.saveColumns.data); | |
4e1046df | 62 | var post = $.post(opts.ajaxCallbacks.saveColumns.url, params, function() { |
3d80d622 CW |
63 | invokeCallback(opts.callbacks.saveColumns, dashboard); |
64 | }); | |
ce2cc43e CW |
65 | if (showStatus !== false) { |
66 | CRM.status({}, post); | |
67 | } | |
3d80d622 | 68 | }; |
3d80d622 CW |
69 | |
70 | /** | |
71 | * Private properties of dashboard. | |
72 | */ | |
31037a42 | 73 | |
3d80d622 CW |
74 | // Used to determine whether two resort events are resulting from the same UI event. |
75 | var currentReSortEvent = null; | |
76 | ||
77 | // Merge in the caller's options with the defaults. | |
78 | var opts = $.extend({}, $.fn.dashboard.defaults, options); | |
79 | ||
4e1046df CW |
80 | var localCache = window.localStorage && localStorage.dashboard ? JSON.parse(localStorage.dashboard) : {}; |
81 | ||
55be4d47 CW |
82 | init(opts.widgetsByColumn); |
83 | ||
3d80d622 | 84 | return dashboard; |
3d80d622 CW |
85 | |
86 | /** | |
87 | * Private methods of dashboard. | |
88 | */ | |
89 | ||
55be4d47 CW |
90 | // Initialize widget columns. |
91 | function init(widgets) { | |
3d80d622 CW |
92 | var markup = '<li class="empty-placeholder">' + opts.emptyPlaceholderInner + '</li>'; |
93 | ||
94 | // Build the dashboard in the DOM. For each column... | |
95 | // (Don't iterate on widgets since this will break badly if the dataset has empty columns.) | |
96 | var emptyDashboard = true; | |
97 | for (var c = 0; c < opts.columns; c++) { | |
98 | // Save the column to both the public scope for external accessibility and the local scope for readability. | |
99 | var col = dashboard.columns[c] = { | |
dd3770bc | 100 | initialWidgets: [], |
3d80d622 CW |
101 | element: $('<ul id="column-' + c + '" class="column column-' + c + '"></ul>').appendTo(dashboard.element) |
102 | }; | |
31037a42 | 103 | |
3d80d622 CW |
104 | // Add the empty placeholder now, hide it and save it. |
105 | col.emptyPlaceholder = $(markup).appendTo(col.element).hide(); | |
106 | ||
107 | // For each widget in this column. | |
dd3770bc CW |
108 | $.each(widgets[c], function(num, item) { |
109 | var id = (num+1) + '-' + item.id; | |
110 | col.initialWidgets[id] = dashboard.widgets[item.id] = widget($.extend({ | |
111 | element: $('<li class="widget"></li>').appendTo(col.element), | |
112 | initialColumn: col | |
113 | }, item)); | |
114 | emptyDashboard = false; | |
115 | }); | |
3d80d622 CW |
116 | } |
117 | ||
dd3770bc CW |
118 | if (emptyDashboard) { |
119 | emptyDashboardCondition(); | |
120 | } else { | |
121 | completeInit(); | |
3d80d622 | 122 | } |
31037a42 | 123 | |
3d80d622 CW |
124 | invokeCallback(opts.callbacks.init, dashboard); |
125 | } | |
126 | ||
127 | // function that is called when dashboard is empty | |
128 | function emptyDashboardCondition( ) { | |
dd3770bc CW |
129 | $(".show-refresh").hide( ); |
130 | $("#empty-message").show( ); | |
3d80d622 | 131 | } |
31037a42 | 132 | |
a8f56d71 | 133 | // Cache dashlet info in localStorage |
4e1046df CW |
134 | function saveLocalCache() { |
135 | localCache = {}; | |
136 | $.each(dashboard.widgets, function(id, widget) { | |
137 | localCache[id] = { | |
138 | content: widget.content, | |
a8f56d71 | 139 | lastLoaded: widget.lastLoaded, |
4e1046df CW |
140 | minimized: widget.minimized |
141 | }; | |
142 | }); | |
143 | if (window.localStorage) { | |
144 | localStorage.dashboard = JSON.stringify(localCache); | |
145 | } | |
146 | } | |
147 | ||
3d80d622 CW |
148 | // Contructors for each widget call this when initialization has finished so that dashboard can complete it's intitialization. |
149 | function completeInit() { | |
4e1046df CW |
150 | // Only do this once. |
151 | if (dashboard.ready) { | |
3d80d622 CW |
152 | return; |
153 | } | |
154 | ||
155 | // Make widgets sortable across columns. | |
156 | dashboard.sortableElement = $('.column').sortable({ | |
157 | connectWith: ['.column'], | |
158 | ||
159 | // The class of the element by which widgets are draggable. | |
160 | handle: '.widget-header', | |
161 | ||
162 | // The class of placeholder elements (the 'ghost' widget showing where the dragged item would land if released now.) | |
31037a42 EM |
163 | placeholder: 'placeholder', |
164 | activate: function(event, ui) { | |
dd3770bc CW |
165 | var h= $(ui.item).height(); |
166 | $('.placeholder').css('height', h +'px'); | |
167 | }, | |
31037a42 | 168 | |
3d80d622 CW |
169 | opacity: 0.2, |
170 | ||
171 | // Maks sure that only widgets are sortable, and not empty placeholders. | |
172 | items: '> .widget', | |
31037a42 | 173 | |
3d80d622 | 174 | forcePlaceholderSize: true, |
31037a42 | 175 | |
3d80d622 CW |
176 | // Callback functions. |
177 | update: resorted, | |
178 | start: hideEmptyPlaceholders | |
179 | }); | |
180 | ||
181 | // Update empty placeholders. | |
182 | dashboard.saveColumns(); | |
183 | dashboard.ready = true; | |
184 | invokeCallback(opts.callbacks.ready, dashboard); | |
a8f56d71 CW |
185 | |
186 | // Auto-refresh widgets when content is stale | |
187 | window.setInterval(function() { | |
188 | if (!document.hasFocus || document.hasFocus()) { | |
189 | $.each(dashboard.widgets, function (i, widget) { | |
190 | if (!widget.cacheIsFresh()) { | |
191 | widget.reloadContent(); | |
192 | } | |
193 | }); | |
194 | } | |
195 | }, 5000); | |
3d80d622 CW |
196 | } |
197 | ||
198 | // Callback for when any list has changed (and the user has finished resorting). | |
199 | function resorted(e, ui) { | |
200 | // Only do anything if we haven't already handled resorts based on changes from this UI DOM event. | |
201 | // (resorted() gets invoked once for each list when an item is moved from one to another.) | |
202 | if (!currentReSortEvent || e.originalEvent != currentReSortEvent) { | |
203 | currentReSortEvent = e.originalEvent; | |
204 | dashboard.saveColumns(); | |
205 | } | |
206 | } | |
207 | ||
208 | // Callback for when a user starts resorting a list. Hides all the empty placeholders. | |
209 | function hideEmptyPlaceholders(e, ui) { | |
210 | for (var c in dashboard.columns) { | |
9e483b7f | 211 | if( (typeof dashboard.columns[c]) == 'object' ) dashboard.columns[c].emptyPlaceholder.hide(); |
3d80d622 CW |
212 | } |
213 | } | |
214 | ||
215 | // @todo use an event library to register, bind to and invoke events. | |
216 | // @param callback is a function. | |
217 | // @param theThis is the context given to that function when it executes. It becomes 'this' inside of that function. | |
218 | function invokeCallback(callback, theThis, parameterOne) { | |
219 | if (callback) { | |
220 | callback.call(theThis, parameterOne); | |
221 | } | |
222 | } | |
223 | ||
224 | /** | |
225 | * widget object | |
226 | * Private sub-class of dashboard | |
227 | * Constructor starts | |
228 | */ | |
229 | function widget(widget) { | |
230 | // Merge default options with the options defined for this widget. | |
4e1046df | 231 | widget = $.extend({}, $.fn.dashboard.widget.defaults, localCache[widget.id] || {}, widget); |
3d80d622 CW |
232 | |
233 | /** | |
234 | * Public methods of widget. | |
235 | */ | |
236 | ||
237 | // Toggles the minimize() & maximize() methods. | |
238 | widget.toggleMinimize = function() { | |
239 | if (widget.minimized) { | |
240 | widget.maximize(); | |
241 | } | |
242 | else { | |
243 | widget.minimize(); | |
244 | } | |
245 | ||
246 | widget.hideSettings(); | |
3d80d622 CW |
247 | }; |
248 | widget.minimize = function() { | |
249 | $('.widget-content', widget.element).slideUp(opts.animationSpeed); | |
dd3770bc CW |
250 | $(widget.controls.minimize.element) |
251 | .addClass('fa-caret-right') | |
252 | .removeClass('fa-caret-down') | |
253 | .attr('title', ts('Expand')); | |
3d80d622 | 254 | widget.minimized = true; |
4e1046df | 255 | saveLocalCache(); |
3d80d622 CW |
256 | }; |
257 | widget.maximize = function() { | |
dd3770bc CW |
258 | $(widget.controls.minimize.element) |
259 | .removeClass( 'fa-caret-right' ) | |
260 | .addClass( 'fa-caret-down' ) | |
261 | .attr('title', ts('Collapse')); | |
3d80d622 | 262 | widget.minimized = false; |
4e1046df | 263 | saveLocalCache(); |
dd3770bc CW |
264 | if (!widget.contentLoaded) { |
265 | loadContent(); | |
266 | } | |
4e1046df | 267 | $('.widget-content', widget.element).slideDown(opts.animationSpeed); |
3d80d622 CW |
268 | }; |
269 | ||
270 | // Toggles whether the widget is in settings-display mode or not. | |
271 | widget.toggleSettings = function() { | |
272 | if (widget.settings.displayed) { | |
273 | // Widgets always exit settings into maximized state. | |
274 | widget.maximize(); | |
275 | widget.hideSettings(); | |
276 | invokeCallback(opts.widgetCallbacks.hideSettings, widget); | |
277 | } | |
278 | else { | |
279 | widget.minimize(); | |
280 | widget.showSettings(); | |
281 | invokeCallback(opts.widgetCallbacks.showSettings, widget); | |
282 | } | |
283 | }; | |
284 | widget.showSettings = function() { | |
285 | if (widget.settings.element) { | |
286 | widget.settings.element.show(); | |
287 | ||
288 | // Settings are loaded via AJAX. Only execute the script if the settings have been loaded. | |
289 | if (widget.settings.ready) { | |
290 | getJavascript(widget.settings.script); | |
291 | } | |
292 | } | |
293 | else { | |
294 | // Settings have not been initialized. Do so now. | |
295 | initSettings(); | |
296 | } | |
297 | widget.settings.displayed = true; | |
298 | }; | |
299 | widget.hideSettings = function() { | |
300 | if (widget.settings.element) { | |
301 | widget.settings.element.hide(); | |
302 | } | |
303 | widget.settings.displayed = false; | |
304 | }; | |
305 | widget.saveSettings = function() { | |
306 | // Build list of parameters to POST to server. | |
307 | var params = {}; | |
308 | // serializeArray() returns an array of objects. Process it. | |
309 | var fields = widget.settings.element.serializeArray(); | |
4e1046df | 310 | $.each(fields, function(i, field) { |
3d80d622 CW |
311 | // Put the values into flat object properties that PHP will parse into an array server-side. |
312 | // (Unfortunately jQuery doesn't do this) | |
313 | params['settings[' + field.name + ']'] = field.value; | |
4e1046df | 314 | }); |
3d80d622 CW |
315 | |
316 | // Things get messy here. | |
31037a42 EM |
317 | // @todo Refactor to use currentState and targetedState properties to determine what needs |
318 | // to be done to get to any desired state on any UI or AJAX event – since these don't always | |
319 | // match. | |
320 | // E.g. When a user starts a new UI event before the Ajax event handler from a previous | |
3d80d622 CW |
321 | // UI event gets invoked. |
322 | ||
323 | // Hide the settings first of all. | |
324 | widget.toggleSettings(); | |
325 | // Save the real settings element so that we can restore the reference later. | |
326 | var settingsElement = widget.settings.element; | |
327 | // Empty the settings form. | |
328 | widget.settings.innerElement.empty(); | |
329 | initThrobber(); | |
330 | // So that showSettings() and hideSettings() can do SOMETHING, without showing the empty settings form. | |
331 | widget.settings.element = widget.throbber.hide(); | |
332 | widget.settings.ready = false; | |
333 | ||
334 | // Save the settings to the server. | |
335 | $.extend(params, opts.ajaxCallbacks.widgetSettings.data, { id: widget.id }); | |
336 | $.post(opts.ajaxCallbacks.widgetSettings.url, params, function(response, status) { | |
337 | // Merge the response into widget.settings. | |
338 | $.extend(widget.settings, response); | |
339 | // Restore the reference to the real settings element. | |
340 | widget.settings.element = settingsElement; | |
341 | // Make sure the settings form is empty and add the updated settings form. | |
342 | widget.settings.innerElement.empty().append(widget.settings.markup); | |
343 | widget.settings.ready = true; | |
344 | ||
345 | // Did the user already jump back into settings-display mode before we could finish reloading the settings form? | |
346 | if (widget.settings.displayed) { | |
347 | // Ooops! We had better take care of hiding the throbber and showing the settings form then. | |
348 | widget.throbber.hide(); | |
349 | widget.showSettings(); | |
350 | invokeCallback(opts.widgetCallbacks.saveSettings, dashboard); | |
351 | } | |
352 | }, 'json'); | |
353 | ||
354 | // Don't let form submittal bubble up. | |
355 | return false; | |
356 | }; | |
357 | ||
358 | widget.enterFullscreen = function() { | |
359 | // Make sure the widget actually supports full screen mode. | |
dd3770bc CW |
360 | if (widget.fullscreenUrl) { |
361 | CRM.loadPage(widget.fullscreenUrl); | |
3d80d622 | 362 | } |
3d80d622 CW |
363 | }; |
364 | ||
365 | // Adds controls to a widget. id is for internal use and image file name in images/dashboard/ (a .gif). | |
366 | widget.addControl = function(id, control) { | |
41ce1b88 | 367 | var markup = '<a class="crm-i ' + control.icon + '" alt="' + control.description + '" title="' + control.description + '"></a>'; |
3d80d622 CW |
368 | control.element = $(markup).prependTo($('.widget-controls', widget.element)).click(control.callback); |
369 | }; | |
370 | ||
4e1046df | 371 | // Fetch remote content. |
3d80d622 | 372 | widget.reloadContent = function() { |
4e1046df CW |
373 | // If minimized, we'll reload later |
374 | if (widget.minimized) { | |
375 | widget.contentLoaded = false; | |
a8f56d71 | 376 | widget.lastLoaded = 0; |
4e1046df CW |
377 | } else { |
378 | CRM.loadPage(widget.url, {target: widget.contentElement}); | |
379 | } | |
3d80d622 CW |
380 | }; |
381 | ||
382 | // Removes the widget from the dashboard, and saves columns. | |
383 | widget.remove = function() { | |
ce2cc43e CW |
384 | invokeCallback(opts.widgetCallbacks.remove, widget); |
385 | widget.element.fadeOut(opts.animationSpeed, function() { | |
386 | $(this).remove(); | |
4e1046df CW |
387 | delete(dashboard.widgets[widget.id]); |
388 | dashboard.saveColumns(false); | |
ce2cc43e | 389 | }); |
ce2cc43e CW |
390 | CRM.alert( |
391 | ts('You can re-add it by clicking the "Configure Your Dashboard" button.'), | |
392 | ts('"%1" Removed', {1: widget.title}), | |
393 | 'success' | |
394 | ); | |
3d80d622 | 395 | }; |
3d80d622 | 396 | |
a8f56d71 CW |
397 | widget.cacheIsFresh = function() { |
398 | return (((widget.cacheMinutes * 60000 + widget.lastLoaded) > $.now()) && widget.content); | |
399 | }; | |
400 | ||
3d80d622 CW |
401 | /** |
402 | * Public properties of widget. | |
403 | */ | |
404 | ||
405 | // Default controls. External script can add more with widget.addControls() | |
406 | widget.controls = { | |
407 | settings: { | |
77a8d7f9 | 408 | description: ts('Configure this dashlet'), |
41ce1b88 AH |
409 | callback: widget.toggleSettings, |
410 | icon: 'fa-wrench' | |
3d80d622 CW |
411 | }, |
412 | minimize: { | |
dd3770bc | 413 | description: widget.minimized ? ts('Expand') : ts('Collapse'), |
41ce1b88 | 414 | callback: widget.toggleMinimize, |
dd3770bc | 415 | icon: widget.minimized ? 'fa-caret-right' : 'fa-caret-down' |
3d80d622 CW |
416 | }, |
417 | fullscreen: { | |
77a8d7f9 | 418 | description: ts('View fullscreen'), |
41ce1b88 | 419 | callback: widget.enterFullscreen, |
55be4d47 | 420 | icon: 'fa-expand' |
3d80d622 CW |
421 | }, |
422 | close: { | |
77a8d7f9 | 423 | description: ts('Remove from dashboard'), |
41ce1b88 AH |
424 | callback: widget.remove, |
425 | icon: 'fa-times' | |
3d80d622 CW |
426 | } |
427 | }; | |
dd3770bc | 428 | widget.contentLoaded = false; |
3d80d622 | 429 | |
dd3770bc | 430 | init(); |
3d80d622 | 431 | return widget; |
3d80d622 CW |
432 | |
433 | /** | |
434 | * Private methods of widget. | |
435 | */ | |
436 | ||
dd3770bc | 437 | function loadContent() { |
a8f56d71 | 438 | var loadFromCache = widget.cacheIsFresh(); |
4e1046df CW |
439 | if (loadFromCache) { |
440 | widget.contentElement.html(widget.content).trigger('crmLoad', widget); | |
441 | } | |
442 | widget.contentElement.off('crmLoad').on('crmLoad', function(event, data) { | |
443 | if ($(event.target).is(widget.contentElement)) { | |
444 | widget.content = data.content; | |
445 | // Cache for one day | |
a8f56d71 | 446 | widget.lastLoaded = $.now(); |
4e1046df | 447 | saveLocalCache(); |
dd3770bc | 448 | invokeCallback(opts.widgetCallbacks.get, widget); |
4e1046df CW |
449 | } |
450 | }); | |
451 | if (!loadFromCache) { | |
452 | widget.reloadContent(); | |
453 | } | |
454 | widget.contentLoaded = true; | |
dd3770bc | 455 | } |
3d80d622 | 456 | |
dd3770bc CW |
457 | // Build widget & load content. |
458 | function init() { | |
3d80d622 CW |
459 | // Delete controls that don't apply to this widget. |
460 | if (!widget.settings) { | |
461 | delete widget.controls.settings; | |
dd3770bc | 462 | widget.settings = {}; |
3d80d622 CW |
463 | } |
464 | if (!widget.fullscreenUrl) { | |
465 | delete widget.controls.fullscreen; | |
466 | } | |
dd3770bc CW |
467 | var cssClass = 'widget-' + widget.name.replace('/', '-'); |
468 | widget.element.attr('id', 'widget-' + widget.id).addClass(cssClass); | |
3d80d622 | 469 | // Build and add the widget's DOM element. |
dd3770bc | 470 | $(widget.element).append(widgetHTML()); |
3d80d622 CW |
471 | // Save the content element so that external scripts can reload it easily. |
472 | widget.contentElement = $('.widget-content', widget.element); | |
473 | $.each(widget.controls, widget.addControl); | |
474 | ||
dd3770bc CW |
475 | if (widget.minimized) { |
476 | widget.contentElement.hide(); | |
477 | } else { | |
478 | loadContent(); | |
479 | } | |
3d80d622 CW |
480 | } |
481 | ||
482 | // Builds inner HTML for widgets. | |
483 | function widgetHTML() { | |
484 | var html = ''; | |
485 | html += '<div class="widget-wrapper">'; | |
486 | html += ' <div class="widget-controls"><h3 class="widget-header">' + widget.title + '</h3></div>'; | |
dd3770bc | 487 | html += ' <div class="widget-content"></div>'; |
3d80d622 CW |
488 | html += '</div>'; |
489 | return html; | |
490 | } | |
491 | ||
492 | // Initializes a widgets settings pane. | |
493 | function initSettings() { | |
494 | // Overwrite widget.settings (boolean). | |
495 | initThrobber(); | |
496 | widget.settings = { | |
497 | element: widget.throbber.show(), | |
498 | ready: false | |
499 | }; | |
500 | ||
501 | // Get the settings markup and script executables for this widget. | |
502 | var params = $.extend({}, opts.ajaxCallbacks.widgetSettings.data, { id: widget.id }); | |
503 | $.getJSON(opts.ajaxCallbacks.widgetSettings.url, params, function(response, status) { | |
504 | $.extend(widget.settings, response); | |
505 | // Build and add the settings form to the DOM. Bind the form's submit event handler/callback. | |
506 | widget.settings.element = $(widgetSettingsHTML()).appendTo($('.widget-wrapper', widget.element)).submit(widget.saveSettings); | |
507 | // Bind the cancel button's event handler too. | |
508 | widget.settings.cancelButton = $('.widget-settings-cancel', widget.settings.element).click(cancelEditSettings); | |
509 | // Build and add the inner form elements from the HTML markup provided in the AJAX data. | |
510 | widget.settings.innerElement = $('.widget-settings-inner', widget.settings.element).append(widget.settings.markup); | |
511 | widget.settings.ready = true; | |
512 | ||
513 | if (widget.settings.displayed) { | |
514 | // If the user hasn't clicked away from the settings pane, then display the form. | |
515 | widget.throbber.hide(); | |
516 | widget.showSettings(); | |
517 | } | |
518 | ||
519 | getJavascript(widget.settings.initScript); | |
520 | }); | |
521 | } | |
522 | ||
523 | // Builds HTML for widget settings forms. | |
524 | function widgetSettingsHTML() { | |
525 | var html = ''; | |
526 | html += '<form class="widget-settings">'; | |
527 | html += ' <div class="widget-settings-inner"></div>'; | |
528 | html += ' <div class="widget-settings-buttons">'; | |
529 | html += ' <input id="' + widget.id + '-settings-save" class="widget-settings-save" value="Save" type="submit" />'; | |
530 | html += ' <input id="' + widget.id + '-settings-cancel" class="widget-settings-cancel" value="Cancel" type="submit" />'; | |
531 | html += ' </div>'; | |
532 | html += '</form>'; | |
533 | return html; | |
534 | } | |
535 | ||
536 | // Initializes a generic widget content throbber, for use by settings form and external scripts. | |
537 | function initThrobber() { | |
538 | if (!widget.throbber) { | |
539 | widget.throbber = $(opts.throbberMarkup).appendTo($('.widget-wrapper', widget.element)); | |
540 | } | |
1a40fe56 | 541 | } |
3d80d622 CW |
542 | |
543 | // Event handler/callback for cancel button clicks. | |
544 | // @todo test this gets caught by all browsers when the cancel button is 'clicked' via the keyboard. | |
545 | function cancelEditSettings() { | |
546 | widget.toggleSettings(); | |
547 | return false; | |
1a40fe56 | 548 | } |
3d80d622 CW |
549 | |
550 | // Helper function to execute external script on the server. | |
551 | // @todo It would be nice to provide some context to the script. How? | |
552 | function getJavascript(url) { | |
553 | if (url) { | |
554 | $.getScript(url); | |
555 | } | |
556 | } | |
1a40fe56 | 557 | } |
3d80d622 CW |
558 | }; |
559 | ||
560 | // Public static properties of dashboard. Default settings. | |
561 | $.fn.dashboard.defaults = { | |
562 | columns: 2, | |
a8f56d71 | 563 | emptyPlaceholderInner: '', |
dd3770bc | 564 | throbberMarkup: '', |
3d80d622 CW |
565 | animationSpeed: 200, |
566 | callbacks: {}, | |
567 | widgetCallbacks: {} | |
568 | }; | |
569 | ||
570 | // Default widget settings. | |
571 | $.fn.dashboard.widget = { | |
572 | defaults: { | |
573 | minimized: false, | |
4e1046df | 574 | content: null, |
a8f56d71 | 575 | lastLoaded: 0, |
dd3770bc | 576 | settings: false |
a8f56d71 | 577 | // id, url, fullscreenUrl, title, name, cacheMinutes |
3d80d622 CW |
578 | } |
579 | }; | |
580 | })(jQuery); |