1 /*********************************************************************************
2 * SugarCRM Community Edition is a customer relationship management program developed by
3 * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
5 * This program is free software; you can redistribute it and/or modify it under
6 * the terms of the GNU Affero General Public License version 3 as published by the
7 * Free Software Foundation with the addition of the following permission added
8 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
9 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
10 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
17 * You should have received a copy of the GNU Affero General Public License along with
18 * this program; if not, see http://www.gnu.org/licenses or write to the Free
19 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
23 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
25 * The interactive user interfaces in modified source and object code versions
26 * of this program must display Appropriate Legal Notices, as required under
27 * Section 5 of the GNU Affero General Public License version 3.
29 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
30 * these Appropriate Legal Notices must retain the display of the "Powered by
31 * SugarCRM" logo. If the display of the logo is not reasonably feasible for
32 * technical reasons, the Appropriate Legal Notices must display the words
33 * "Powered by SugarCRM".
34 ********************************************************************************/
37 YAHOO.namespace("SUGAR");
40 Event = YAHOO.util.Event,
41 Connect = YAHOO.util.Connect,
45 * Message Box is a singleton widget designed to replace the browsers 'alert'
46 * function, as well as provide capabilities for pop-over loading bars and
47 * other small non-interactive pop-overs.
48 * TODO:Still needs configurable buttons in the footer as well as
49 * auto building of a loading bar.
52 progressTemplate : "{body}<br><div class='sugar-progress-wrap'><div class='sugar-progress-bar'/></div>",
53 promptTemplate : "{body}:<input id='sugar-message-prompt' class='sugar-message-prompt' name='sugar-message-prompt'></input>",
54 show: function(config) {
55 var myConf = sw.MessageBox.config = {
66 if (config['type'] && config['type'] == "prompt") {
67 myConf['buttons'] = [{
68 text: SUGAR.language.get("app_strings", "LBL_EMAIL_CANCEL"), handler:YAHOO.SUGAR.MessageBox.hide
70 text: SUGAR.language.get("app_strings", "LBL_EMAIL_OK"), handler:config['fn'] ?
72 var returnValue = config['fn'](YAHOO.util.Dom.get("sugar-message-prompt").value);
73 if (typeof(returnValue) == "undefined" || returnValue) {
74 YAHOO.SUGAR.MessageBox.hide();
77 : YAHOO.SUGAR.MessageBox.hide, isDefault:true
79 } else if ((config['type'] && config['type'] == "alert")) {
80 myConf['buttons'] = [{
81 text: SUGAR.language.get("app_strings", "LBL_EMAIL_OK"),
82 handler: config['fn'] ?
83 function(){YAHOO.SUGAR.MessageBox.hide(); config['fn']();}
85 YAHOO.SUGAR.MessageBox.hide,
88 } else if((config['type'] && config['type'] == "confirm")) {
89 myConf['buttons'] = [{
90 text: SUGAR.language.get("app_strings", "LBL_EMAIL_YES"), handler:config['fn'] ?
91 function(){config['fn']('yes');YAHOO.SUGAR.MessageBox.hide();}
92 : YAHOO.SUGAR.MessageBox.hide, isDefault:true
94 text: SUGAR.language.get("app_strings", "LBL_EMAIL_NO"), handler:config['fn'] ?
95 function(){config['fn']('no');YAHOO.SUGAR.MessageBox.hide();}
96 : YAHOO.SUGAR.MessageBox.hide
99 else if((config['type'] && config['type'] == "plain")) {
100 myConf['buttons'] = [];
103 for (var i in config) {
104 myConf[i] = config[i];
106 if (sw.MessageBox.panel) {
107 sw.MessageBox.panel.destroy();
109 sw.MessageBox.panel = new YAHOO.widget.SimpleDialog(myConf.id, {
110 width: myConf.width + 'px',
115 constraintoviewport: true,
117 buttons: myConf.buttons
119 if (myConf.type == "progress") {
120 sw.MessageBox.panel.setBody(sw.MessageBox.progressTemplate.replace(/\{body\}/gi, myConf.msg));
121 } else if (myConf.type == "prompt") {
122 sw.MessageBox.panel.setBody(sw.MessageBox.promptTemplate.replace(/\{body\}/gi, myConf.msg));
123 } else if (myConf.type == "confirm") {
124 sw.MessageBox.panel.setBody(myConf.msg);
126 sw.MessageBox.panel.setBody(myConf.msg);
128 sw.MessageBox.panel.setHeader(myConf.title);
130 if (myConf.beforeShow) {
131 sw.MessageBox.panel.beforeShowEvent.subscribe(function() {myConf.beforeShow();});
133 if (myConf.beforeHide) {
134 sw.MessageBox.panel.beforeHideEvent.subscribe(function() {myConf.beforeHide();});
136 sw.MessageBox.panel.render(document.body);
137 sw.MessageBox.panel.show();
140 updateProgress: function(percent, message) {
141 if (!sw.MessageBox.config.type == "progress") return;
143 if (typeof message == "string") {
144 sw.MessageBox.panel.setBody(sw.MessageBox.progressTemplate.replace(/\{body\}/gi, message));
147 var barEl = Dom.getElementsByClassName("sugar-progress-bar", null, YAHOO.SUGAR.MessageBox.panel.element)[0];
150 else if (percent < 0)
153 barEl.style.width = percent + "%";
157 if (sw.MessageBox.panel)
158 sw.MessageBox.panel.hide();
162 sw.Template = function(content) {
163 this._setContent(content);
166 sw.Template.prototype = {
167 regex : /\{([\w\.]*)\}/gim,
169 append: function (target, args) {
170 var tEl = Dom.get(target);
171 if (tEl) tEl.innerHTML += this.exec(args);
172 else if (typeof(console) != "undefined" && typeof(console.log) == "function")
173 console.log("Warning, unable to find target:" + target);
175 exec : function (args) {
176 var out = this.content;
177 for (var i in this.vars) {
178 var val = this._getValue(i, args);
179 var reg = new RegExp("\\{" + i + "\\}", "g");
180 out = out.replace(reg, val);
185 _setContent : function(content) {
186 this.content = content;
188 var result = this.regex.exec(content);
190 while(result && result.index > lastIndex){
191 lastIndex = result.index;
192 this.vars[result[1]] = true;
193 result = this.regex.exec(content);
197 _getValue: function(v, scope) {
198 return function(e) {return eval("this." + e);}.call(scope, v);
204 * SelectionGrid is simply a YUI Data Table with row selection already enabled.
206 sw.SelectionGrid = function(containerEl, columns, dataSource, config){
207 sw.SelectionGrid.superclass.constructor.call(this, containerEl, columns, dataSource, config);
208 // Subscribe to events for row selection
209 this.subscribe("rowMouseoverEvent", this.onEventHighlightRow);
210 this.subscribe("rowMouseoutEvent", this.onEventUnhighlightRow);
211 if (config.forceMulti){
212 this.subscribe("rowClickEvent", function(o){
213 o.event.preventDefault();
214 this.clearTextSelection();
215 o.event = SUGAR.util.clone(o.event);
216 o.event.ctrlKey = o.event.metaKey = true;
217 this.onEventSelectRow(o);
220 this.subscribe("rowClickEvent", this.onEventSelectRow);
223 // Programmatically select the first row
224 this.selectRow(this.getTrEl(0));
225 // Programmatically bring focus to the instance so arrow selection works immediately
229 YAHOO.extend(sw.SelectionGrid, YAHOO.widget.ScrollingDataTable, {
230 //Bugfix, the default getColumn will fail if a th element is passed in. http://yuilibrary.com/projects/yui2/ticket/2528034
231 getColumn : function(column) {
232 var oColumn = this._oColumnSet.getColumn(column);
235 // Validate TD element
236 var elCell = this.getTdEl(column);
237 if(elCell && (!column.tagName || column.tagName.toUpperCase() != "TH")) {
238 oColumn = this._oColumnSet.getColumn(elCell.cellIndex);
240 // Validate TH element
242 elCell = this.getThEl(column);
246 var allColumns = this._oColumnSet.flat;
247 for(var i=0, len=allColumns.length; i<len; i++) {
248 if(allColumns[i].getThEl().id === elCell.id) {
249 oColumn = allColumns[i];
256 YAHOO.log("Could not get Column for column at " + column, "info", this.toString());
264 * DragDropTable is a YUI Data Table with support for drag/drop row re-ordering.
266 sw.DragDropTable = function(containerEl, columns, dataSource, config){
267 var DDT = sw.DragDropTable;
268 DDT.superclass.constructor.call(this, containerEl, columns, dataSource, config);
269 this.DDGroup = config.group ? config.group : "defGroup";
270 //Add table to the dragdrop table groups
271 if (typeof DDT.groups[this.DDGroup] == "undefined")
272 DDT.groups[this.DDGroup] = [];
274 DDT.groups[this.DDGroup][DDT.groups[this.DDGroup].length] = this;
275 this.tabledd = new YAHOO.util.DDTarget(containerEl);
277 sw.DragDropTable.groups = {
281 YAHOO.extend(sw.DragDropTable, YAHOO.widget.ScrollingDataTable, {
282 _addTrEl : function (oRecord) {
283 var elTr = sw.DragDropTable.superclass._addTrEl.call(this, oRecord);
284 if (!this.disableEmptyRows || (
285 oRecord.getData()[this.getColumnSet().keys[0].key] != false
286 && oRecord.getData()[this.getColumnSet().keys[0].key] != "")
288 var _rowDD = new sw.RowDD(this, oRecord, elTr);
292 getGroup : function () {
293 return sw.DragDropTable.groups[this.DDGroup];
298 * subclass of DragDrop to allow rows to be picked up and dropped between other rows.
300 sw.RowDD = function(oDataTable, oRecord, elTr) {
301 if(oDataTable && oRecord && elTr) {
302 //sw.RowDD.superclass.constructor.call(this, elTr);
303 this.ddtable = oDataTable;
304 this.table = oDataTable.getTableEl();
307 this.newIndex = null;
309 this.initFrame(); // Needed for DDProxy
310 this.invalidHandleTypes = {};
315 YAHOO.extend(sw.RowDD, YAHOO.util.DDProxy, {
316 // _removeIdRegex : /(<.[^\/<]*)id\s*=\s*['|"]?\w*['|"]?([^>]*>)/gim,
317 _removeIdRegex : new RegExp("(<.[^\\/<]*)id\\s*=\\s*['|\"]?\w*['|\"]?([^>]*>)", "gim"),
319 _resizeProxy: function() {
320 this.constructor.superclass._resizeProxy.apply(this, arguments);
321 var dragEl = this.getDragEl(),
324 Dom.setStyle(this.pointer, 'height', (this.rowEl.offsetHeight + 5) + 'px');
325 Dom.setStyle(this.pointer, 'display', 'block');
326 var xy = Dom.getXY(el);
327 Dom.setXY(this.pointer, [xy[0], (xy[1] - 5)]);
329 Dom.setStyle(dragEl, 'height', this.rowEl.offsetHeight + "px");
330 Dom.setStyle(dragEl, 'width', (parseInt(Dom.getStyle(dragEl, 'width'),10) + 4) + 'px');
331 Dom.setXY(this.dragEl, xy);
334 startDrag: function(x, y) {
335 var dragEl = this.getDragEl();
336 var clickEl = this.getEl();
337 Dom.setStyle(clickEl, "opacity", "0.25");
338 var tableWrap = false;
339 if (clickEl.tagName.toUpperCase() == "TR")
341 dragEl.innerHTML = "<table>" + clickEl.innerHTML.replace(this._removeIdRegex, "$1$2") + "</table>";
342 //Dom.setStyle(dragEl, "color", Dom.getStyle(clickEl, "color"));
343 Dom.addClass(dragEl, "yui-dt-liner");
344 Dom.setStyle(dragEl, "height", (clickEl.clientHeight - 2) + "px");
345 Dom.setStyle(dragEl, "backgroundColor", Dom.getStyle(clickEl, "backgroundColor"));
346 Dom.setStyle(dragEl, "border", "2px solid gray");
347 Dom.setStyle(dragEl, "display", "");
349 this.newTable = this.ddtable;
352 clickValidator: function(e) {
353 if (this.row.getData()[0] == " ")
355 var target = Event.getTarget(e);
356 return ( this.isValidHandleChild(target) &&
357 (this.id == this.handleElId || this.DDM.handleWasClicked(target, this.id)) );
360 * This function checks that the target of the drag is a table row in this
361 * DDGroup and simply moves the sourceEL to that location as a preview.
363 onDragOver: function(ev, id) {
364 var groupTables = this.ddtable.getGroup();
365 for(i in groupTables) {
366 var targetTable = groupTables[i];
367 if (!targetTable.getContainerEl)
370 if (targetTable.getContainerEl().id == id) {
371 if (targetTable != this.newTable) { // Moved from one table to another
372 this.newIndex = targetTable.getRecordSet().getLength() - 1;
373 var destEl = Dom.get(targetTable.getLastTrEl());
374 destEl.parentNode.insertBefore(this.getEl(), destEl);
377 this.newTable = targetTable
382 if (this.newTable && this.newTable.getRecord(id)) { // Found the target row
383 var targetRow = this.newTable.getRecord(id);
384 var destEl = Dom.get(id);
385 destEl.parentNode.insertBefore(this.getEl(), destEl);
386 this.newIndex = this.newTable.getRecordIndex(targetRow);
390 endDrag: function() {
391 //Ensure the element is back on the home table to be cleaned up.
392 if (this.newTable != null && this.newIndex != null) {
393 this.getEl().style.display = "none";
394 this.table.appendChild(this.getEl());
395 this.newTable.addRow(this.row.getData(), this.newIndex);
397 //This method works, but throws an error under IE8
398 this.ddtable.deleteRow(this.row);
400 if(typeof(console) != "undefined" && console.log)
405 this.ddtable.render();
407 this.newTable = this.newIndex = null
409 var clickEl = this.getEl();
410 Dom.setStyle(clickEl, "opacity", "");
414 * A YUI panel that supports loading and re-loading it's contents from an Asynch request.
417 sw.AsyncPanel = function (el, params) {
419 sw.AsyncPanel.superclass.constructor.call(this, el, params);
421 sw.AsyncPanel.superclass.constructor.call(this, el);
424 YAHOO.extend(sw.AsyncPanel, YAHOO.widget.Panel, {
425 loadingText : "Loading...",
426 failureText : "Error loading content.",
428 load : function(url, method, callback, postdata) {
429 method = method ? method : "GET";
430 this.setBody(this.loadingText);
431 if (Connect.url) url = Connect.url + "&" + url;
432 this.callback = callback;
433 Connect.asyncRequest(method, url, {success:this._updateContent, failure:this._loadFailed, scope:this}, postdata);
436 _updateContent : function (o) {
437 //Under safari, the width of the panel may expand for no apparent reason, and under FF it will contract
438 var w = this.cfg.config.width.value + "px";
439 this.setBody(o.responseText);
441 this.body.style.width = w
442 if (this.callback != null)
446 _loadFailed : function(o) {
447 this.setBody(this.failureText);
451 sw.ClosableTab = function(el, parent, conf) {
452 this.closeEvent = new YAHOO.util.CustomEvent("close", this);
454 sw.ClosableTab.superclass.constructor.call(this, el, conf);
456 sw.ClosableTab.superclass.constructor.call(this, el);
458 this.setAttributeConfig("TabView", {
461 this.get("labelEl").parentNode.href = "javascript:void(0);";
464 YAHOO.extend(sw.ClosableTab, YAHOO.widget.Tab, {
465 close : function () {
466 this.closeEvent.fire();
467 var parent = this.get("TabView");
468 parent.removeTab(this);
471 initAttributes: function(attr) {
472 sw.ClosableTab.superclass.initAttributes.call(this, attr);
475 * The message to display when closing the tab
476 * @attribute closeMsg
479 this.setAttributeConfig("closeMsg", {
480 value: attr.closeMsg || ""
484 * The tab's label text (or innerHTML).
488 this.setAttributeConfig("label", {
489 value: attr.label || this._getLabel(),
490 method: function(value) {
491 var labelEl = this.get("labelEl");
492 if (!labelEl) { // create if needed
493 this.set(LABEL_EL, this._createLabelEl());
496 labelEl.innerHTML = value;
498 var closeButton = document.createElement('a');
499 closeButton.href = "javascript:void(0);";
500 Dom.addClass(closeButton, "sugar-tab-close");
501 Event.addListener(closeButton, "click", function(e, tab){
502 if (tab.get("closeMsg") != "")
504 if (confirm(tab.get("closeMsg"))) {
514 labelEl.appendChild(closeButton);
523 * The sugar Tree is a YUI tree with node construction based on AJAX data built in.
525 sw.Tree = function (parentEl, baseRequestParams, rootParams) {
526 this.baseRequestParams = baseRequestParams;
527 sw.Tree.superclass.constructor.call(this, parentEl);
529 if (typeof rootParams == "string")
530 this.sendTreeNodeDataRequest(this.getRoot(), rootParams);
532 this.sendTreeNodeDataRequest(this.getRoot(), "");
536 YAHOO.extend(sw.Tree, YAHOO.widget.TreeView, {
537 sendTreeNodeDataRequest: function(parentNode, params){
538 YAHOO.util.Connect.asyncRequest('POST', 'index.php', {
539 success: this.handleTreeNodeDataRequest,
541 parentNode: parentNode
544 }, this.baseRequestParams + params);
546 handleTreeNodeDataRequest : function(o) {
547 var parentNode = o.argument.parentNode;
548 //parent.tree.removeChildren(parentNode);
549 var resp = YAHOO.lang.JSON.parse(o.responseText);
550 if (resp.tree_data.nodes) {
551 for (var i = 0; i < resp.tree_data.nodes.length; i++) {
552 var newChild = this.buildTreeNodeRecursive(resp.tree_data.nodes[i], parentNode);
555 parentNode.tree.draw();
558 buildTreeNodeRecursive : function(nodeData, parentNode) {
559 nodeData.label = nodeData.text;
560 var node = new YAHOO.widget.TextNode(nodeData, parentNode, nodeData.expanded);
561 if (typeof(nodeData.children) == 'object') {
562 for (var i = 0; i < nodeData.children.length; i++) {
563 this.buildTreeNodeRecursive(nodeData.children[i], node);
572 * A 1/2 second fade-in animation.
575 * @param el {HTMLElement} the element to animate
576 * @param callback {function} function to invoke when the animation is finished
578 YAHOO.widget.TVSlideIn = function(el, callback) {
580 * The element to animate
587 * the callback to invoke when the animation is complete
591 this.callback = callback;
593 this.logger = new YAHOO.widget.LogWriter(this.toString());
596 YAHOO.widget.TVSlideIn.prototype = {
598 * Performs the animation
601 animate: function() {
604 var s = this.el.style;
607 s.overflow = "hidden";
609 var th = this.el.clientHeight;
613 var a = new YAHOO.util.Anim(this.el, {height: {from: 0, to: th, unit:"px"}}, dur);
614 a.onComplete.subscribe( function() { tvanim.onComplete(); } );
619 * Clean up and invoke callback
622 onComplete: function() {
623 this.el.style.overflow = "";
624 this.el.style.height = "";
631 * @return {string} the string representation of the instance
633 toString: function() {
639 * A 1/2 second fade out animation.
642 * @param el {HTMLElement} the element to animate
643 * @param callback {Function} function to invoke when the animation is finished
645 YAHOO.widget.TVSlideOut = function(el, callback) {
647 * The element to animate
654 * the callback to invoke when the animation is complete
658 this.callback = callback;
660 this.logger = new YAHOO.widget.LogWriter(this.toString());
663 YAHOO.widget.TVSlideOut.prototype = {
665 * Performs the animation
668 animate: function() {
671 var th = this.el.clientHeight;
672 this.el.style.overflow = "hidden";
673 var a = new YAHOO.util.Anim(this.el, {height: {from: th, to: 0, unit:"px"}}, dur);
674 a.onComplete.subscribe( function() { tvanim.onComplete(); } );
679 * Clean up and invoke callback
682 onComplete: function() {
683 var s = this.el.style;
685 this.el.style.overflow = "";
686 this.el.style.height = "";
693 * @return {string} the string representation of the instance
695 toString: function() {
704 * Problem: SODA tests rely on grabbing report field names by IDs. However,
705 * these ID's don't always seem to be consistent as they are auto-generated by YUI.
706 * Here I have replaced the constructor to set the id of the row element to the modulename
707 * concatenated with the fieldname if the report flag has been set, otherwise proceed as normal.
712 var temp = YAHOO.widget.Record.prototype;
713 YAHOO.widget.Record = function(oLiteral) {
714 this._nCount = YAHOO.widget.Record._nCount;
716 YAHOO.widget.Record._nCount++;
718 if (YAHOO.lang.isObject(oLiteral)) {
719 for (var sKey in oLiteral) {
720 if (YAHOO.lang.hasOwnProperty(oLiteral, sKey)) {
721 this._oData[sKey] = oLiteral[sKey];
726 if (SUGAR.reports && SUGAR.reports.overrideRecord) {
727 this._sId = this._oData.module_name + "_" + this._oData.field_name;
729 this._sId = Dom.generateId(null, "yui-rec"); //"yui-rec" + this._nCount;
732 YAHOO.widget.Record._nCount = 0;
733 YAHOO.widget.Record.prototype = temp;