diff --git a/LICENSE b/LICENSE index 469070092..02c4f78bb 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2013-2016 WaveMaker, Inc. + Copyright 2013-2017 WaveMaker, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 219925feb..6d8921ec1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # WaveMaker App Runtime +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fwavemaker%2Fwavemaker-app-runtime.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fwavemaker%2Fwavemaker-app-runtime?ref=badge_shield) + WaveMaker App Runtime is an application library packaged within every app built by WaveMaker RAD platform. App Runtime is open sourced under Apache 2.0 License, giving complete rights to App Developers to freely modify and distribute applications built using WaveMaker. @@ -7,13 +9,19 @@ App Runtime comprises of [Angular JS](https://angularjs.org/) based Widget libra ![](http://www.wavemaker.com/wp-content/uploads/9.png "WaveMaker") ## Widget Library +http://www.wavemaker.com/learn/app-development/widgets/widget-library/ ## Hybrid Mobile Widgets and Plugins +Building hybrid mobile applications based on Cordova using WaveMaker Studio + +http://www.wavemaker.com/learn/hybrid-mobile/building-hybrid-mobile-apps/ + +List of plugins/device widgets -## Spring and Hibernate Services +http://www.wavemaker.com/learn/app-development/widgets/widget-library/#mobile ## Release Notes -[Latest Release Notes](http://www.wavemaker.com/learn/docs/wavemaker-studio-release-notes/) +[Latest Release Notes](http://www.wavemaker.com/learn/wavemaker-release-notes/) ## Related Projects * [WaveMaker Sample Apps](https://github.com/wavemaker/wavemaker-sample-apps) @@ -22,12 +30,14 @@ App Runtime comprises of [Angular JS](https://angularjs.org/) based Widget libra * [WaveMaker Icon Library, Wavicon](https://github.com/wavemaker/wavicon) ## Dependent Projects -* [WaveMaker Studio Commons](https://github.com/wavemaker/wavemaker-studio-commons) -* [WaveMaker API Tools]() +* [WaveMaker Commons](https://github.com/wavemaker/wavemaker-commons) +* [WaveMaker API Tools](https://github.com/wavemaker/wavemaker-tools-apidocs) +* [WaveMaker App Build Tools](https://github.com/wavemaker/wavemaker-app-build-tools) * [Angular UI Bootstrap](https://github.com/angular-ui/bootstrap) * [ngCordova](https://github.com/driftyco/ng-cordova/) -## Build Instructions - ## Licensing WaveMaker App Runtime is licensed under Apache License, Version 2.0. See [License](/LICENSE) for full license text. + + +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fwavemaker%2Fwavemaker-app-runtime.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fwavemaker%2Fwavemaker-app-runtime?ref=badge_large) \ No newline at end of file diff --git a/pom.xml b/pom.xml index 0e99d2229..e18a4d948 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 - com.wavemaker.studio + com.wavemaker.runtime wavemaker-app-runtime 8.3.4 @@ -17,7 +17,7 @@ UTF-8 UTF-8 - 2.7 + 2.13 1.7.21 1.2.17 @@ -54,9 +54,12 @@ 0.9.5.1 1.8.3 - 3.15 - 3.15 + 3.10.1 + 3.10.1 2.2.3 + 1.4 + 6.2.2 + 2.10.0 @@ -425,7 +428,6 @@ hibernate-entitymanager ${hibernate.version} - com.mchange c3p0 @@ -463,10 +465,25 @@ ${commons.lang3.version} + + xerces + xercesImpl + ${xerces.version} + net.sf.jmimemagic jmimemagic ${mimemagic.version} + + + + xerces + xercesImpl + + org.apache.poi @@ -477,6 +494,12 @@ org.apache.poi poi-ooxml ${poi.ooxml.version} + + + com.github.virtuald + curvesapi + + com.fasterxml.jackson.core @@ -565,12 +588,6 @@ com.sun.xml.ws jaxws-rt 2.1.7 - - - stax - stax-api - - com.sun.xml.ws @@ -599,6 +616,24 @@ 1.1.1 provided + + xml-apis + xml-apis + 1.4.01 + provided + + + xml-apis + xmlParserAPIs + 2.0.2 + provided + + + stax + stax-api + 1.0.1 + provided + javax.xml.stream stax-api @@ -654,11 +689,6 @@ woodstox-core-asl 4.2.1 - - org.hamcrest - hamcrest-all - 1.3 - org.json json @@ -760,7 +790,35 @@ org.springframework.security.extensions spring-security-saml2-core 1.0.2.RELEASE + + + xml-apis + xml-apis + + + + + + org.owasp + antisamy + ${antisamy.version} + + + + net.sf.jasperreports + jasperreports + ${jasperreports.version} + + + + + org.quartz-scheduler + quartz + ${quartz.version} + + + @@ -785,55 +843,74 @@ junit junit 4.11 + test org.hamcrest hamcrest-core + + + org.hamcrest + hamcrest-all + 1.3 test org.mockito mockito-all 1.8.5 + test + + org.testng + testng + 6.9.6 + test + + + com.wavemaker.commons + wavemaker-commons-util + ${project.version} + test-jar + test + + - - log4j - log4j - org.slf4j slf4j-api - - org.slf4j - jcl-over-slf4j - + junit junit - test org.springframework spring-test - test org.hamcrest hamcrest-all - test org.mockito mockito-all - test + + + org.testng + testng + + + com.wavemaker.commons + wavemaker-commons-util + test-jar diff --git a/src/main/webapp/Gruntfile.js b/src/main/webapp/Gruntfile.js index dacd0fb44..f5edfbda2 100644 --- a/src/main/webapp/Gruntfile.js +++ b/src/main/webapp/Gruntfile.js @@ -654,6 +654,7 @@ module.exports = function (grunt) { '<%= config.scripts %>/modules/mobile/widgets/device/fileBrowser/deviceMediaService.js', '<%= config.scripts %>/modules/mobile/widgets/form/fileupload/fileupload.js', '<%= config.scripts %>/modules/mobile/plugins/database/services/localDBManager.js', + '<%= config.scripts %>/modules/mobile/plugins/database/services/localKeyValueService.js', '<%= config.scripts %>/modules/mobile/plugins/database/services/localDBStoreFactory.js', '<%= config.scripts %>/modules/mobile/plugins/database/services/localDBService.js', '<%= config.scripts %>/modules/mobile/plugins/offline/config.js', diff --git a/src/main/webapp/bower.json b/src/main/webapp/bower.json index 5bce81233..1a0957f1c 100644 --- a/src/main/webapp/bower.json +++ b/src/main/webapp/bower.json @@ -23,7 +23,7 @@ "lodash": "https://github.com/lodash/lodash.git#4.16.4", "hammerjs": "2.0.8", "animate.css": "4.0.0.alpha-1", - "fullcalendar": "2.7.1", + "fullcalendar": "3.0.1", "angular-ui-calendar": "1.0.1", "moment": "2.15.1", "ngCordova":"v0.1.27-alpha", @@ -35,7 +35,7 @@ "resolutions": { "d3": "v3.5.17", "angular": "1.5.8", - "fullcalendar": "2.7.1" + "fullcalendar": "3.0.1" }, "exportsOverride": { "jquery-ui": { diff --git a/src/main/webapp/scripts/liveWidgetUtils.js b/src/main/webapp/scripts/liveWidgetUtils.js index e1e78cf7b..05e2368da 100644 --- a/src/main/webapp/scripts/liveWidgetUtils.js +++ b/src/main/webapp/scripts/liveWidgetUtils.js @@ -266,18 +266,18 @@ WM.module('wm.widgets.live') 'double' : ['number', 'text', 'select', 'checkboxset', 'radioset', 'slider', 'currency', 'autocomplete', 'chips'], 'long' : ['number', 'text', 'select', 'checkboxset', 'radioset', 'rating', 'slider', 'currency', 'autocomplete', 'chips'], 'byte' : ['number', 'text', 'select', 'checkboxset', 'radioset', 'slider', 'currency', 'autocomplete', 'chips'], - 'string' : ['text', 'number', 'textarea', 'password', 'richtext', 'select', 'checkboxset', 'radioset', 'date', 'time', 'timestamp', 'switch', 'currency', 'autocomplete', 'chips'], + 'string' : ['text', 'number', 'textarea', 'password', 'richtext', 'select', 'checkboxset', 'radioset', 'date', 'time', 'timestamp', 'switch', 'currency', 'autocomplete', 'chips', 'colorpicker'], 'character' : ['text', 'number', 'textarea', 'password', 'richtext', 'select', 'checkboxset', 'radioset', 'switch', 'currency', 'autocomplete', 'chips'], - 'text' : ['text', 'number', 'textarea', 'password', 'richtext', 'select', 'checkboxset', 'radioset', 'date', 'time', 'timestamp', 'switch', 'currency', 'autocomplete', 'chips'], + 'text' : ['text', 'number', 'textarea', 'password', 'richtext', 'select', 'checkboxset', 'radioset', 'date', 'time', 'timestamp', 'switch', 'currency', 'autocomplete', 'chips', 'colorpicker'], 'date' : ['date', 'text', 'number', 'select', 'checkboxset', 'radioset', 'autocomplete', 'chips'], 'time' : ['time', 'text', 'number', 'select', 'checkboxset', 'radioset', 'autocomplete', 'chips'], 'timestamp' : ['timestamp', 'text', 'number', 'select', 'checkboxset', 'radioset', 'autocomplete', 'chips'], 'datetime' : ['datetime', 'text', 'select', 'checkboxset', 'radioset', 'autocomplete', 'chips'], - 'boolean' : ['checkbox', 'radioset', 'toggle', 'select', 'autocomplete', 'chips'], - 'list' : ['select', 'radioset', 'checkboxset', 'text', 'number', 'switch', 'autocomplete', 'chips'], - 'clob' : ['text', 'number', 'select', 'textarea', 'richtext', 'autocomplete', 'chips'], - 'blob' : ['upload', 'text', 'number', 'select', 'textarea', 'richtext', 'autocomplete', 'chips'], - 'custom' : ['text', 'number', 'textarea', 'password', 'checkbox', 'slider', 'richtext', 'currency', 'switch', 'select', 'checkboxset', 'radioset', 'date', 'time', 'timestamp', 'upload', 'rating', 'datetime', 'autocomplete', 'chips'] + 'boolean' : ['checkbox', 'radioset', 'toggle', 'select'], + 'list' : ['select', 'radioset', 'checkboxset', 'switch', 'autocomplete', 'chips'], + 'clob' : ['text', 'textarea', 'richtext'], + 'blob' : ['upload'], + 'custom' : ['text', 'number', 'textarea', 'password', 'checkbox', 'slider', 'richtext', 'currency', 'switch', 'select', 'checkboxset', 'radioset', 'date', 'time', 'timestamp', 'rating', 'datetime', 'autocomplete', 'chips', 'colorpicker'] }; return fieldTypeWidgetTypeMap; } @@ -460,7 +460,7 @@ WM.module('wm.widgets.live') return fields; } - function getDataSetFields(fieldDef, index) { + function getDataSetFields(fieldDef, index, $el) { var template; if (fieldDef.widget === 'autocomplete' || fieldDef.widget === 'typeahead') { template = ' datafield="{{formFields[' + index + '].datafield}}" searchkey="{{formFields[' + index + '].searchkey}}" displaylabel="{{formFields[' + index + '].displaylabel}}"'; @@ -468,7 +468,13 @@ WM.module('wm.widgets.live') template = ' datafield="{{formFields[' + index + '].datafield}}" displayfield="{{formFields[' + index + '].displayfield}}"'; } if (!fieldDef.dataset) { - template = template + ' scopedataset="formFields[' + index + '].dataset" dataset="" '; + //In studio mode, set default option instead of scopedataset and add representational data indicator + if (CONSTANTS.isStudioMode && $el) { + template = template + ' dataset="Option 1, Option 2, Option 3" '; + $el.attr('data-evaluated-dataset', ''); + } else { + template = template + ' scopedataset="formFields[' + index + '].dataset" dataset="" '; + } } else { template = template + ' dataset="{{formFields[' + index + '].dataset}}" '; } @@ -478,7 +484,7 @@ WM.module('wm.widgets.live') function getDefaultTemplate(widgetType, fieldDef, index, minPlaceholderDefault, maxPlaceholderDefault, defaultPlaceholder, additionalFields, isCustomWidget) { var template = '', widgetName = 'wm-' + widgetType, - updateModeCondition = isCustomWidget ? '' : (widgetType === 'richtexteditor' ? 'show = "bind:isUpdateMode"' : 'show="{{isUpdateMode}}"'), + updateModeCondition = isCustomWidget ? '' : (widgetType === 'richtexteditor' ? 'show = "bind:isUpdateMode"' : 'ng-if="isUpdateMode"'), allowInvalidAttr = fieldDef.widget === 'number' ? ' allowinvalid=true ' : '', readonly = (widgetType !== 'richtexteditor' || fieldDef.readonly ? 'readonly="{{!isUpdateMode || formFields[' + index + '].readonly}}"' : ''); additionalFields = additionalFields || ''; @@ -486,11 +492,11 @@ WM.module('wm.widgets.live') fieldDef.placeholder = fieldDef.displayformat ? '' : (_.isUndefined(fieldDef.placeholder) ? minPlaceholderDefault : fieldDef.placeholder); fieldDef.maxPlaceholder = fieldDef.displayformat ? '' : (_.isUndefined(fieldDef.maxPlaceholder) ? maxPlaceholderDefault : fieldDef.maxPlaceholder); template = template + - '
<' + widgetName + ' ' + getFormFields(fieldDef, index, widgetType) + ' scopedatavalue="formFields[' + index + '].minValue" placeholder="{{formFields[' + index + '].placeholder}}" readonly="{{!isUpdateMode || formFields[' + index + '].readonly}}"' + allowInvalidAttr + updateModeCondition + additionalFields + '>
' + - '
<' + widgetName + ' ' + getFormFields(fieldDef, index, widgetType) + ' scopedatavalue="formFields[' + index + '].maxValue" placeholder="{{formFields[' + index + '].maxPlaceholder}}" readonly="{{!isUpdateMode || formFields[' + index + '].readonly}}"' + allowInvalidAttr + updateModeCondition + additionalFields + '>
'; + '
<' + widgetName + ' ' + getFormFields(fieldDef, index, widgetType) + ' scopedatavalue="formFields[' + index + '].minValue" placeholder="' + fieldDef.placeholder + '" readonly="{{!isUpdateMode || formFields[' + index + '].readonly}}"' + allowInvalidAttr + updateModeCondition + additionalFields + '>
' + + '
<' + widgetName + ' ' + getFormFields(fieldDef, index, widgetType) + ' scopedatavalue="formFields[' + index + '].maxValue" placeholder="' + fieldDef.maxPlaceholder + '" readonly="{{!isUpdateMode || formFields[' + index + '].readonly}}"' + allowInvalidAttr + updateModeCondition + additionalFields + '>
'; } else { fieldDef.placeholder = fieldDef.displayformat ? '' : (_.isUndefined(fieldDef.placeholder) ? defaultPlaceholder : fieldDef.placeholder); - template = template + '<' + widgetName + ' ' + getFormFields(fieldDef, index, widgetType) + ' scopedatavalue="formFields[' + index + '].value" placeholder="{{formFields[' + index + '].placeholder}}"' + readonly + allowInvalidAttr + updateModeCondition + additionalFields + '>'; + template = template + '<' + widgetName + ' ' + getFormFields(fieldDef, index, widgetType) + ' scopedatavalue="formFields[' + index + '].value" placeholder="' + fieldDef.placeholder + '"' + readonly + allowInvalidAttr + updateModeCondition + additionalFields + '>'; } return template; } @@ -549,6 +555,10 @@ WM.module('wm.widgets.live') return getDefaultTemplate('slider', fieldDef, index, '', '', '', additionalFields); } + function getColorPickerTemplate(fieldDef, index) { + return getDefaultTemplate('colorpicker', fieldDef, index, 'Select Color', 'Select Color', 'Select Color'); + } + /*Returns chips template */ function getChipsTemplate(fieldDef, index) { var additionalFields = getDataSetFields(fieldDef, index); @@ -556,14 +566,14 @@ WM.module('wm.widgets.live') } /*Returns radioset template */ - function getRadiosetTemplate(fieldDef, index) { - var additionalFields = getDataSetFields(fieldDef, index); + function getRadiosetTemplate(fieldDef, index, $el) { + var additionalFields = getDataSetFields(fieldDef, index, $el); return getDefaultTemplate('radioset', fieldDef, index, '', '', '', additionalFields); } /*Returns checkboxset template */ - function getCheckboxsetTemplate(fieldDef, index) { - var additionalFields = getDataSetFields(fieldDef, index); + function getCheckboxsetTemplate(fieldDef, index, $el) { + var additionalFields = getDataSetFields(fieldDef, index, $el); return getDefaultTemplate('checkboxset', fieldDef, index, '', '', '', additionalFields); } @@ -592,8 +602,8 @@ WM.module('wm.widgets.live') return getDefaultTemplate('rating', fieldDef, index, '', '', '', additionalFields, true); } - function getSwitchTemplate(fieldDef, index) { - var additionalFields = getDataSetFields(fieldDef, index); + function getSwitchTemplate(fieldDef, index, $el) { + var additionalFields = getDataSetFields(fieldDef, index, $el); return getDefaultTemplate('switch', fieldDef, index, '', '', '', additionalFields); } @@ -630,13 +640,13 @@ WM.module('wm.widgets.live') * @description * return template based on widgetType for liveFilter and liveForm. */ - function getTemplate(fieldDef, index, captionPosition) { + function getTemplate(fieldDef, index, captionPosition, $el) { var template = '', widgetType, fieldTypeWidgetTypeMap = getFieldTypeWidgetTypesMap(), labelLayout, controlLayout, - show = CONSTANTS.isRunMode ? 'show="{{formFields[' + index + '].show}}"' : ''; + displayLabel = ''; captionPosition = captionPosition || 'top'; //Set 'Readonly field' placeholder for fields which are readonly and contain generated values if the user has not given any placeholder if (fieldDef.readonly && fieldDef.generator === 'identity') { @@ -655,13 +665,19 @@ WM.module('wm.widgets.live') controlLayout = $rs.isMobileApplicationType ? 'col-xs-8' : 'col-sm-9'; } //Construct the template based on the Widget Type, if widget type is not set refer to the fieldTypeWidgetTypeMap - widgetType = fieldDef.widget || fieldTypeWidgetTypeMap[fieldDef.type][0]; - widgetType = widgetType.toLowerCase(); - template = template + - '' + - '' + + widgetType = fieldDef.widget || fieldTypeWidgetTypeMap[fieldDef.type][0]; + widgetType = widgetType.toLowerCase(); + if (fieldDef.displayname) { //Add label field, only if displayname is given + displayLabel = ''; + } else { + controlLayout = $rs.isMobileApplicationType ? 'col-xs-12' : 'col-sm-12'; + } + //If displayname is bound, set to empty value. This is to prevent bind: showing up in label + fieldDef.displayname = (CONSTANTS.isRunMode && _.startsWith(fieldDef.displayname, 'bind:')) ? '' : fieldDef.displayname; + template = template + + '
' + displayLabel + '
' + - ''; + ''; switch (widgetType) { case 'number': @@ -677,14 +693,17 @@ WM.module('wm.widgets.live') template += getCheckboxTemplate(fieldDef, index, widgetType); break; case 'checkboxset': - template += getCheckboxsetTemplate(fieldDef, index); + template += getCheckboxsetTemplate(fieldDef, index, $el); break; case 'radioset': - template += getRadiosetTemplate(fieldDef, index); + template += getRadiosetTemplate(fieldDef, index, $el); break; case 'slider': template += getSliderTemplate(fieldDef, index); break; + case 'colorpicker': + template += getColorPickerTemplate(fieldDef, index); + break; case 'chips': template += getChipsTemplate(fieldDef, index); break; @@ -711,7 +730,7 @@ WM.module('wm.widgets.live') template += getRatingTemplate(fieldDef, index); break; case 'switch': - template += getSwitchTemplate(fieldDef, index); + template += getSwitchTemplate(fieldDef, index, $el); break; case 'currency': template += getCurrencyTemplate(fieldDef, index); @@ -726,7 +745,7 @@ WM.module('wm.widgets.live') } template = template + (fieldDef.hint ? '

{{formFields[' + index + '].hint}}

' : ''); template = template + '

{{formFields[' + index + '].validationmessage}}

'; - template = template + '
'; + template = template + '
'; return template; } @@ -945,24 +964,35 @@ WM.module('wm.widgets.live') * @param {string} newVal new value for the key */ function fieldPropertyChangeHandler(scope, element, attrs, parentScope, index, key, newVal) { - var template = '', + var template = '', wdgtProperties = scope.widgetProps, - compileField = function () { - if (CONSTANTS.isRunMode) { - //On changing of a property in studio mode, generate the template again so that change is reflected - template = getTemplate(parentScope.formFields[index], index, parentScope.captionposition); - //Destroy the scopes of the widgtes inside the form field - element.find('.ng-isolate-scope') - .each(function () { - WM.element(this).isolateScope().$destroy(); - }); - //Remove only live-field so that overlay won't get overrided - element.find('.live-field').remove(); - element.append(template); - $compile(element.contents())(parentScope); - } - }, - formWidget = parentScope.Widgets[scope.name + '_formWidget']; + formWidget = parentScope.Widgets[scope.name + '_formWidget']; + + function compileField() { + if (CONSTANTS.isRunMode) { + //On changing of a property in studio mode, generate the template again so that change is reflected + template = getTemplate(parentScope.formFields[index], index, parentScope.captionposition); + //Destroy the scopes of the widgtes inside the form field + element.find('.ng-isolate-scope') + .each(function () { + var elIscope = WM.element(this).isolateScope(); + if (elIscope) { + elIscope.$destroy(); + } + }); + //Remove only live-field so that overlay won't get overrided + element.find('.live-field').remove(); + element.append(template); + $compile(element.contents())(parentScope); + } + } + + function setFormField() { + if (CONSTANTS.isRunMode) { + parentScope.formFields[index][key] = newVal; + } + } + if (formWidget && key !== 'show') { formWidget[key] = newVal; //Set the property on the form widget inside the form field widget } @@ -977,6 +1007,7 @@ WM.module('wm.widgets.live') if (scope.widget === 'autocomplete') { FormWidgetUtils.updatePropertyOptionsWithParams(scope); //update searchkey options in case of service variables } + element.removeAttr('data-evaluated-dataset'); } break; case 'inputtype': @@ -987,15 +1018,19 @@ WM.module('wm.widgets.live') Utils.getService('LiveWidgetsMarkupManager').updateFieldMarkup({'formName': parentScope.name, 'fieldName': scope.name}); element.parents('[widgettype="wm-gridcolumn"]').removeClass('hide'); } - parentScope.formFields[index][key] = newVal; + setFormField(); compileField(); break; + case 'displayname': + element.find('label.formfield-label').attr('title', newVal).text(newVal); + setFormField(); + break; case 'disabled': case 'readonly': case 'required': case 'validationmessage': case 'hint': - parentScope.formFields[index][key] = newVal; + setFormField(); break; case 'active': if (scope.widget === 'number' || scope.widget === 'password' || scope.widget === 'text') { @@ -1054,6 +1089,10 @@ WM.module('wm.widgets.live') baseProperties = 'wm.slider'; extendedProperties = ['wm.base', 'wm.base.events.change']; break; + case 'colorpicker': + baseProperties = 'wm.colorpicker'; + extendedProperties = ['wm.base', 'wm.base.events', 'wm.base.events.focus', 'wm.base.events.change']; + break; case 'chips': baseProperties = 'wm.chips'; extendedProperties = ['wm.base', 'wm.base.editors.dataseteditors']; @@ -1301,6 +1340,10 @@ WM.module('wm.widgets.live') column.customExpression = ''; break; + case 'icon': + column.customExpression = ''; + break; default: if (column.type === 'blob') { column.customExpression = ''; @@ -1356,6 +1399,15 @@ WM.module('wm.widgets.live') 'ngClasses': widgetNgClasses }; break; + case 'icon': + column.widgetConfig = { + 'title' : val, + 'class' : '', + 'icon' : 'wi wi-star-border', + 'iconposition' : 'left', + 'ngClasses' : widgetNgClasses + }; + break; default: column.widgetConfig = { 'src': val, @@ -1436,6 +1488,17 @@ WM.module('wm.widgets.live') 'ngClasses' : widgetNgClasses }; break; + case 'icon': + widgetTitle = el.attr('caption'); + widgetIcon = el.attr('iconclass'); + column.widgetConfig = { + 'title' : widgetTitle, + 'icon' : widgetIcon, + 'class' : widgetClass, + 'iconposition' : el.attr('iconposition') || 'left', + 'ngClasses' : widgetNgClasses + }; + break; default: column.widgetConfig = { 'src' : '', @@ -1525,7 +1588,7 @@ WM.module('wm.widgets.live') }; } - function fetchReferenceDetails($scope) { + function fetchReferenceDetails($scope, elScope) { var referenceWidget, referenceBindDataSet, referenceVariableName, @@ -1538,7 +1601,7 @@ WM.module('wm.widgets.live') bindDataSetSplit, bindDataSet = $scope.binddataset, widgetRegEx = /Widgets./g, - WidgetScopes = $scope.Widgets, + WidgetScopes = elScope ? elScope.Widgets : $scope.Widgets, isBoundToSelectedItemSubset = bindDataSet.indexOf('selecteditem.') !== -1; //Get the reference widget name. As widget can be inner widget (like Widgets.tab.Widgets.grid), find the last inner widget while (widgetRegEx.exec(bindDataSet) !== null) { @@ -1575,13 +1638,13 @@ WM.module('wm.widgets.live') } return details; } - function fetchDynamicData($scope, success, error) { + function fetchDynamicData($scope, elScope, success, error) { var reference, referenceVariableKey, watchSelectedItem, referenceVariable; /*Invoke the function to fetch the reference variable details when a grid2 is bound to another grid1 and grid1 is bound to a variable.*/ - reference = fetchReferenceDetails($scope); + reference = fetchReferenceDetails($scope, elScope); referenceVariable = Variables.getVariableByName(reference.referenceVariableName); /*Check if a watch is not registered on selectedItem or if the relatedField is a one-to-many relation because this field value will directly be available in the data*/ if ($scope.selectedItemWatched || !referenceVariable || !referenceVariable.isRelatedFieldMany(reference.relatedFieldName)) { @@ -1826,43 +1889,9 @@ WM.module('wm.widgets.live') * function to get view mode widgets for grid */ function getViewModeWidgets() { - return ['image', 'button', 'checkbox', 'label', 'anchor']; - } - /** - * @ngdoc function - * @name wm.widgets.live.setCaptionSize - * @methodOf wm.widgets.live.LiveWidgetUtils - * @function - * - * @description - * Set the width of all labels in the form to the caption size - * @param {formEle} element from which widgets needs to be retrieved - * @param {value} width of the label - */ - function setCaptionSize(formEle, value) { - formEle.find('.form-group .app-label.ng-isolate-scope').each(function () { - WM.element(this).isolateScope().width = value; - }); + return ['image', 'button', 'checkbox', 'label', 'anchor', 'icon']; } - /** - * @ngdoc function - * @name wm.widgets.live.closeDialog - * @methodOf wm.widgets.live.LiveWidgetUtils - * @function - * - * @description - * This function closes the design dialog that form is wrapped - * - * @param {ele} element whose parent is dialog - */ - function closeDialog(ele) { - var dialogEle; - dialogEle = ele.closest('.app-dialog'); - if (dialogEle.length) { - DialogService.hideDialog(dialogEle.attr('dialogid')); - } - } /** * @ngdoc function * @name wm.widgets.live.fetchRelatedFieldData @@ -1952,8 +1981,6 @@ WM.module('wm.widgets.live') this.getFormFilterWidgets = getFormFilterWidgets; this.getViewModeWidgets = getViewModeWidgets; this.parseNgClasses = parseNgClasses; - this.setCaptionSize = setCaptionSize; - this.closeDialog = closeDialog; this.fetchRelatedFieldData = fetchRelatedFieldData; this.getEditModeWidget = getEditModeWidget; } @@ -2016,11 +2043,6 @@ WM.module('wm.widgets.live') Utils.triggerFn(error, err); }); }; - if (variable.propertiesMap && variable.propertiesMap.tableType === 'VIEW') { - wmToaster.show('info', 'Not Editable', 'Table of type view, not editable'); - $rs.$safeApply(options.scope); - return; - } DialogService._showAppConfirmDialog({ 'caption' : 'Delete Record', 'content' : confirmMsg, diff --git a/src/main/webapp/scripts/modules/common/services/baseService.js b/src/main/webapp/scripts/modules/common/services/baseService.js index f6608006c..7c346177a 100644 --- a/src/main/webapp/scripts/modules/common/services/baseService.js +++ b/src/main/webapp/scripts/modules/common/services/baseService.js @@ -127,6 +127,7 @@ wm.modules.wmCommon.services.BaseService = [ /* set extra config flags */ config.byPassResult = serviceParams.byPassResult; config.isDirectCall = serviceParams.isDirectCall; + config.isExtURL = serviceParams.isExtURL; config.preventMultiple = serviceParams.preventMultiple; return config; @@ -180,14 +181,28 @@ wm.modules.wmCommon.services.BaseService = [ return returnVal; }, + getLoginErrorMsg = function (error) { + return WM.isFunction(error.headers) && error.headers('X-WM-Login-ErrorMessage') + }, + + isPlatformSessionTimeout = function (error) { + var MSG_SESSION_NOT_FOUND = 'Session Not Found'; + return error.status === 401 && getLoginErrorMsg(error) === MSG_SESSION_NOT_FOUND; + }, + + isLoginFailure = function (error) { + var MSG_LOGIN_FAILURE = 'Authentication Failed: Bad credentials'; + return error.status === 401 && getLoginErrorMsg(error) === MSG_LOGIN_FAILURE; + }, + failureHandler = function (config, successCallback, failureCallback, error) { - var errTitle, errMsg, errorDetails = error, appManager, isLoginFailure, + var errTitle, errMsg, errorDetails = error, appManager, HTTP_STATUS_MSG = { - 404: "Requested resource not found" + 404: "Requested resource not found", + 401: "Requested resource requires authentication" }; - isLoginFailure = WM.isFunction(error.headers) && error.headers('X-WM-Login-ErrorMessage'); /*if user is unauthorized, then show login dialog*/ - if (error.status === 401 && !isLoginFailure && !config.isDirectCall) { + if (isPlatformSessionTimeout(error) && !config.isDirectCall) { if (CONSTANTS.isRunMode && config.url !== 'app.variables.json') { /* * a failed app.variables.json file doesn't need to be re-invoked always after login @@ -221,8 +236,8 @@ wm.modules.wmCommon.services.BaseService = [ errMsg = localeObject["MESSAGE_ERROR_HTTP_STATUS_ERROR_DESC"]; } else { /*assigning default error messages */ - errTitle = "Error!"; - errMsg = "Service call failed"; + errTitle = 'Error!'; + errMsg = 'Service call failed'; } // check if error message present for responded http status @@ -238,11 +253,13 @@ wm.modules.wmCommon.services.BaseService = [ errMsg += parseError(errorDetails) + (i > 0 ? "\n" : ""); }); } + } else if (CONSTANTS.isRunMode && config.isExtURL && !_.isEmpty(error.data)) {//Show the actual error for restService case + errMsg = error.data; } /* check for login failure header */ - if (isLoginFailure) { - errMsg = isLoginFailure; + if (isLoginFailure(error)) { + errMsg = getLoginErrorMsg(error); } /*check if failureCallback is defined*/ @@ -401,10 +418,6 @@ wm.modules.wmCommon.services.BaseService = [ handleSessionTimeOut = function () { if (!isUnAuthorized) { var isStudioDisabled, dialogId = CONSTANTS.isStudioMode ? 'sessionTimeOutDialog' : 'CommonLoginDialog'; - if (!CONSTANTS.isStudioMode) { - Utils.triggerFn($rootScope.onSessionTimeout); - $rootScope.$emit('on-sessionTimeout'); - } isStudioDisabled = $rootScope.isStudioDisabled; $rootScope.isStudioDisabled = false; DialogService.closeAllDialogs(); diff --git a/src/main/webapp/scripts/modules/common/services/navigationService.js b/src/main/webapp/scripts/modules/common/services/navigationService.js index 1998cf2f0..818459ea4 100644 --- a/src/main/webapp/scripts/modules/common/services/navigationService.js +++ b/src/main/webapp/scripts/modules/common/services/navigationService.js @@ -173,20 +173,31 @@ wm.modules.wmCommon.services.NavigationService = [ } } + function stopLoginPagePostLogin($p) { + SecurityService.getConfig(function (config) { + if (config.securityEnabled && config.authenticated && pageName === config.loginConfig.pageName) { + $location.path(_.get($p, 'params.name') || _.get(config, 'userInfo.landingPage') || _.get(config.homePage)); + return; + } + }); + } + $rs.$on('$routeChangeStart', function (evt, $next, $p) { var pageName = $next.params.name; if (pageName) { - // if login page is being loaded and user is logged in, cancel that. - if ($rs.isApplicationType) { - SecurityService.getConfig(function (config) { - if (config.securityEnabled && config.authenticated && pageName === config.login.pageName) { - $location.path(_.get($p, 'params.name') || _.get(config, 'userInfo.landingPage') || _.get(config.homePage)); - return; - } - }); - } + /* + * Commenting this code, one client project has Home_Page configured as Login Page. + * So redirection to Home_Page post login is failing + // if login page is being loaded and user is logged in, cancel that. + if ($rs.isApplicationType) { + stopLoginPagePostLogin($p); + } + */ if (pageStackObject.isLastVisitedPage(pageName)) { - nextTransitionToApply = pageStackObject.getCurrentPage().transition + '-exit'; + nextTransitionToApply = pageStackObject.getCurrentPage().transition; + if (!_.isEmpty(nextTransitionToApply)) { + nextTransitionToApply += '-exit'; + } pageStackObject.goBack(); } else { pageStackObject.addPage({ @@ -194,8 +205,10 @@ wm.modules.wmCommon.services.NavigationService = [ 'transition' : nextTransitionToApply }); } - WM.element('#wm-app-content:first').addClass('page-transition-' + nextTransitionToApply); - nextTransitionToApply = ''; + if (!_.isEmpty(nextTransitionToApply)) { + $next.transition = 'page-transition page-transition-' + nextTransitionToApply; + nextTransitionToApply = ''; + } } }); diff --git a/src/main/webapp/scripts/modules/layouts/containers/accordion/accordion.js b/src/main/webapp/scripts/modules/layouts/containers/accordion/accordion.js index 014ea3623..619053287 100644 --- a/src/main/webapp/scripts/modules/layouts/containers/accordion/accordion.js +++ b/src/main/webapp/scripts/modules/layouts/containers/accordion/accordion.js @@ -9,7 +9,7 @@ WM.module('wm.layouts.containers') $templateCache.put('template/layout/container/accordion-pane.html', '
' + - '
' + + '
' + '

' + '' + '
' + @@ -28,7 +28,24 @@ WM.module('wm.layouts.containers') .directive('wmAccordion', ['$templateCache', 'WidgetUtilService', 'PropertiesFactory', 'Utils', function ($templateCache, WidgetUtilService, PropertiesFactory, Utils) { 'use strict'; - var widgetProps = PropertiesFactory.getPropertiesOf('wm.accordion', ['wm.base', 'wm.layouts.panel.defaults']); + var widgetProps = PropertiesFactory.getPropertiesOf('wm.accordion', ['wm.base', 'wm.layouts.panel.defaults']), + notifyFor = { + 'defaultpaneindex': true + }; + + /*Define the property change handler. This function will be triggered when there is a change in the widget property */ + function propertyChangeHandler(scope, key, newVal) { + switch (key) { + case 'defaultpaneindex': + //If no activepane is set ie.. no isdefaultpane then honor defaultpaneindex + if (!scope.activePane) { + scope.activePane = scope.panes[newVal || 0]; + } + scope.activePane.expand(); + + break; + } + } return { 'restrict': 'E', @@ -38,18 +55,21 @@ WM.module('wm.layouts.containers') 'template': $templateCache.get('template/layout/container/accordion.html'), 'controller': function ($scope) { /* Contains the isolateScopes of accordion-panes. */ - this.panes = []; + $scope.panes = []; + this.paneIndex = 0; /* save the scope of the accordion-pane */ this.register = function (paneScope) { - this.panes.push(paneScope); + $scope.panes.push(paneScope); + paneScope.paneId = this.paneIndex; + this.paneIndex++; }; /* function to collapse the accordion-panes */ this.closeOthers = function () { /* process the request only when closeothers attribute is present on accordion */ if ($scope.closeothers) { - WM.forEach(this.panes, function (pane) { + WM.forEach($scope.panes, function (pane) { if (pane.isActive) { /* trigger the onCollapse method on the pane which is about to be collapsed */ Utils.triggerFn(pane.onCollapse); @@ -66,9 +86,15 @@ WM.module('wm.layouts.containers') scope.widgetProps = attrs.widgetid ? Utils.getClonedObject(widgetProps) : widgetProps; }, 'post': function (scope, element, attrs, ctrl) { - var defaultPane; - defaultPane = _.find(ctrl.panes, function (pane) { return pane.isdefaultpane; }) || ctrl.panes[0]; - defaultPane.expand(); + + _.forEach(scope.panes, function (pane) { + if (pane.isdefaultpane && !attrs.defaultpaneindex) { + scope.activePane = pane; + } + }); + + /* register the property change handler */ + WidgetUtilService.registerPropertyChangeListener(propertyChangeHandler.bind(undefined, scope), scope, notifyFor); WidgetUtilService.postWidgetCreate(scope, element, attrs); } @@ -101,6 +127,9 @@ WM.module('wm.layouts.containers') element.removeAttr('title'); }, 'post': function (scope, element, attrs, panesCtrl) { + + var parentScope = element.closest('.app-accordion').isolateScope(); + //To support backward compatibility for old projects if (scope.title === undefined && !scope.bindtitle) { scope.title = scope.heading || scope.bindheading; @@ -109,15 +138,23 @@ WM.module('wm.layouts.containers') panesCtrl.register(scope); /* toggle the state of the pane */ - scope.togglePane = function () { + scope.togglePane = function ($event) { /* flip the active flag */ var flag = !scope.isActive; + if (flag) { /* some widgets like charts needs to be redrawn when a accordion pane becomes active for the first time */ element.find('.ng-isolate-scope') .each(function () { Utils.triggerFn(WM.element(this).isolateScope().redraw); }); + + if (!scope.widgetid && parentScope.onChange && $event) { + parentScope.onChange({'$event': $event, '$scope': parentScope, 'newPaneIndex': scope.paneId, 'oldPaneIndex': (parentScope.activePane && parentScope.activePane.paneId) || 0}); + } + + parentScope.activePane = scope; + // when pane content is set to display external page, triggering $lazyLoad on expand of the accordion pane will render the content. Utils.triggerFn(scope.$lazyLoad); /* trigger the onExpand call back */ @@ -183,6 +220,11 @@ WM.module('wm.layouts.containers') * True value for closeothers property will collapse the panes that are expanded on expand of a pane.
* False value for closeothers property will not collapse the expaneded panes on expand of a pane.
* Default value: `true`. + * @param {number=} defaultpaneindex + * Makes the pane active for given index.This property has backward compatibility for isdefaultpane property.
+ * Default value: 0 + * @param {string=} on-change + * Callback function which will be triggered when the widget value is changed. * * @example @@ -247,10 +289,6 @@ WM.module('wm.layouts.containers') * @param {string=} horizontalalign * Align the content of the accordion-header to left/right/center.
* Default value: `left`. - * @param {boolean=} isdefaultpane - * This is a bindable property.
- * It will be used to make one accordion pane open by default.
- * Default value: `false`. * @param {string=} on-expand * Callback function which will be triggered when the pane is expanded. * @param {string=} on-collapse diff --git a/src/main/webapp/scripts/modules/layouts/containers/form/form.js b/src/main/webapp/scripts/modules/layouts/containers/form/form.js index a3920654d..87f7a8b33 100644 --- a/src/main/webapp/scripts/modules/layouts/containers/form/form.js +++ b/src/main/webapp/scripts/modules/layouts/containers/form/form.js @@ -4,7 +4,7 @@ WM.module('wm.layouts.containers') .run(['$templateCache', function ($templateCache) { 'use strict'; $templateCache.put('template/layout/container/form.html', - '
' + '
' + '

' + @@ -29,7 +29,7 @@ WM.module('wm.layouts.containers') }]) .directive('wmForm', ['$rootScope', 'PropertiesFactory', 'WidgetUtilService', '$compile', 'CONSTANTS', 'Utils', '$timeout', 'LiveWidgetUtils', "wmToaster", function ($rootScope, PropertiesFactory, WidgetUtilService, $compile, CONSTANTS, Utils, $timeout, LiveWidgetUtils, wmToaster) { 'use strict'; - var widgetProps = PropertiesFactory.getPropertiesOf('wm.layouts.form', ['wm.base', 'wm.base.events.touch','wm.layouts.panel.defaults']), + var widgetProps = PropertiesFactory.getPropertiesOf('wm.layouts.form', ['wm.base', 'wm.base.events.touch', 'wm.layouts.panel.defaults']), notifyFor = { 'captionsize' : true, 'novalidate' : true, @@ -112,13 +112,13 @@ WM.module('wm.layouts.containers') var value, resetBtnTemplate, $gridLayout; switch (key) { case 'captionsize': - LiveWidgetUtils.setCaptionSize(element, newVal); + scope.elScope.captionsize = newVal; break; case 'captionalign': scope.captionAlignClass = "align-" + newVal; break; case 'captionposition': - scope.captionPositionClass = "position-" + newVal; + scope.elScope.captionposition = newVal; break; case 'novalidate': if (newVal === true || newVal === 'true') { @@ -230,17 +230,34 @@ WM.module('wm.layouts.containers') function toggleMessage(scope, msg, type) { if (msg) { - if (scope.messagelayout === 'Inline') { - scope.statusMessage = {'caption': msg || '', type: type}; - } else { - wmToaster.show(type, type.toUpperCase(), msg, undefined, 'trustedHtml'); + $rootScope.$evalAsync(function () { + if (scope.messagelayout === 'Inline') { + scope.statusMessage = {'caption': msg || '', type: type}; + } else { + wmToaster.show(type, type.toUpperCase(), msg, undefined, 'trustedHtml'); + } + }); + } + } + + //Get the variable bound to form + function getFormVariable(scope, element) { + //If binddataset is available and starts with bind:Variables, extract the variable name + if (scope.binddataset) { + if (_.includes(scope.binddataset, 'bind:Variables.')) { + scope.formVariable = Utils.getVariableNameFromExpr(scope.binddataset); + } else if (scope.dataset) { + scope.formVariable = scope.dataset; } + } else if (scope.dataset) { + scope.formVariable = scope.dataset; } + return element.scope().Variables[scope.formVariable]; } function constructDataObject(scope, element) { var formData = {}, - formVariable = element.scope().Variables[scope.formVariable]; + formVariable = getFormVariable(scope, element); //Get all form fields and prepare form data as key value pairs _.forEach(scope.elScope.formFields, function (field) { var fieldName, @@ -266,7 +283,7 @@ WM.module('wm.layouts.containers') var params, template, formData, - formVariable = element.scope().Variables[scope.formVariable]; + formVariable = getFormVariable(scope, element); resetFormState(scope); //Set the values of the widgets inside the form (other than form fields) in form data formData = scope.constructDataObject(); @@ -279,29 +296,35 @@ WM.module('wm.layouts.containers') if (scope.onSubmit || formVariable) { //If on submit is there execute it and if it returns true do service variable invoke else return //If its a service variable call setInput and assign form data and invoke the service - if (formVariable && formVariable.category === 'wm.ServiceVariable') { - formVariable.setInput(formData); - formVariable.update({ - 'skipNotification': true - }, function (data) { - toggleMessage(scope, scope.postmessage, 'success'); - onResult(scope, data, 'success', event); - Utils.triggerFn(scope.onSubmit, params); - LiveWidgetUtils.closeDialog(element); - }, function (errMsg) { - template = scope.errormessage || errMsg; - toggleMessage(scope, template, 'error'); - onResult(scope, errMsg, 'error', event); + if (formVariable) { + if (formVariable.category === 'wm.ServiceVariable') { + formVariable.setInput(formData); + formVariable.update({ + 'skipNotification': true + }, function (data) { + toggleMessage(scope, scope.postmessage, 'success'); + onResult(scope, data, 'success', event); + Utils.triggerFn(scope.onSubmit, params); + }, function (errMsg) { + template = scope.errormessage || errMsg; + toggleMessage(scope, template, 'error'); + onResult(scope, errMsg, 'error', event); + Utils.triggerFn(scope.onSubmit, params); + }); + } else { + /* invoking the variable in a timeout, so that the current variable dataSet values are updated before invoking */ + $timeout(function () { + $rootScope.$emit('invoke-service', formVariable.name, {scope: scope}); + }); Utils.triggerFn(scope.onSubmit, params); - }); - } else if (formVariable) { - /* invoking the variable in a timeout, so that the current variable dataSet values are updated before invoking */ - $timeout(function () { - $rootScope.$emit('invoke-service', formVariable.name, {scope: scope}); - }); + onResult(scope, {}, 'success', event); + } + } else { Utils.triggerFn(scope.onSubmit, params); - LiveWidgetUtils.closeDialog(element); + onResult(scope, {}, 'success', event); } + } else { + onResult(scope, {}, 'success', event); } } function bindEvents(scope, element) { @@ -339,9 +362,6 @@ WM.module('wm.layouts.containers') var handlers = []; scope.statusMessage = undefined; - if (scope.binddataset) { - scope.formVariable = Utils.getVariableNameFromExpr(scope.binddataset); - } /* register the property change handler */ WidgetUtilService.registerPropertyChangeListener(propertyChangeHandler.bind(undefined, scope, element, attrs), scope, notifyFor); @@ -351,9 +371,6 @@ WM.module('wm.layouts.containers') scope.reset = resetForm.bind(undefined, scope, element); scope.submit = submitForm.bind(undefined, scope, element); } else { - //binddataset: allowing user click on the binded dataset. - scope.widgetProps.dataset.show = !!scope.binddataset; - //event emitted on building new markup from canvasDom handlers.push($rootScope.$on('compile-form-fields', function (event, scopeId, markup) { //as multiple form directives will be listening to the event, apply field-definitions only for current form diff --git a/src/main/webapp/scripts/modules/layouts/containers/mediaList/mediaList.js b/src/main/webapp/scripts/modules/layouts/containers/mediaList/mediaList.js index d49658360..3bd2a6fe2 100644 --- a/src/main/webapp/scripts/modules/layouts/containers/mediaList/mediaList.js +++ b/src/main/webapp/scripts/modules/layouts/containers/mediaList/mediaList.js @@ -45,7 +45,8 @@ WM.module('wm.layouts.containers') 'WidgetUtilService', 'CONSTANTS', 'Utils', - function ($templateCache, $compile, PropertiesFactory, WidgetUtilService, CONSTANTS, Utils) { + 'DeviceService', + function ($templateCache, $compile, PropertiesFactory, WidgetUtilService, CONSTANTS, Utils, DeviceService) { 'use strict'; var widgetProps = PropertiesFactory.getPropertiesOf('wm.medialist', ['wm.base', 'wm.base.editors']), notifyFor = { @@ -187,7 +188,17 @@ WM.module('wm.layouts.containers') function runMode_postLinkFn($is, $el, attrs, listCtrl) { var $mediaTemplate, - $mediaScope = createChildScope($is, $el, listCtrl); + $mediaScope = createChildScope($is, $el, listCtrl), + backButtonListenerDeregister; + backButtonListenerDeregister = DeviceService.onBackButtonTap(function () { + if ($is.selectedMediaIndex >= 0) { + $is.selectedMediaIndex = -1; + return false; + } + }); + $is.$on('$destroy', function () { + backButtonListenerDeregister(); + }); $is.$mediaScope = $mediaScope; $mediaTemplate = prepareMediaListTemplate(listCtrl.$get('mediaListTemplate'), attrs); $el.find('> ul').append($mediaTemplate); diff --git a/src/main/webapp/scripts/modules/layouts/containers/nav/nav.js b/src/main/webapp/scripts/modules/layouts/containers/nav/nav.js index e9ef4ede0..bd4a6fee3 100644 --- a/src/main/webapp/scripts/modules/layouts/containers/nav/nav.js +++ b/src/main/webapp/scripts/modules/layouts/containers/nav/nav.js @@ -13,8 +13,9 @@ WM.module('wm.layouts.containers') '$routeParams', 'CONSTANTS', 'FormWidgetUtils', + '$window', - function (Utils, PropertiesFactory, WidgetUtilService, $rs, $compile, $routeParams, CONSTANTS, FormWidgetUtils) { + function (Utils, PropertiesFactory, WidgetUtilService, $rs, $compile, $routeParams, CONSTANTS, FormWidgetUtils, $window) { 'use strict'; var widgetProps = PropertiesFactory.getPropertiesOf('wm.layouts.nav', ['wm.containers']), notifyFor = { @@ -74,60 +75,70 @@ WM.module('wm.layouts.containers') labelField = $is.itemlabel || 'label', itemField = $is.itemlink || 'link', badgeField = $is.itembadge || 'badge', - childrenField = $is.itemchildren || 'children'; - - $is.nodes.forEach(function (node, index) { - - var $a = WM.element(''), - $a_caption = WM.element(''), - $li = WM.element('
  • ').data('node-data', node), - $i = WM.element(''), - $badge = WM.element(''), - itemLabel = node[labelField], - itemClass = node[iconField], - itemLink = node[itemField], - itemBadge = node[badgeField], - itemChildren = node[childrenField], - $menu; - - // menu widget expects data as an array. - // push the current object as an array into the internal array - $is._nodes.push(node[childrenField]); - - if ($routeParams.name === (itemLink && itemLink.substring(1))) { - $a.addClass('active'); - } + childrenField = $is.itemchildren || 'children', + actionField = $is.itemaction || 'action'; - if (itemChildren && WM.isArray(itemChildren)) { - - $menu = WM.element(''); - - $menu.attr({ - 'caption' : itemLabel, - 'dataset' : 'bind:_nodes['+ index +']', - 'itemlabel' : labelField, - 'itemlink' : itemField, - 'itemicon' : iconField, - 'itemchildren': childrenField, - 'type' : 'anchor', - 'iconclass' : itemClass || '', - 'on-select' : '_onMenuItemSelect($event, $item)', - 'autoclose' : $is.autoclose - }); + $is.nodes = $is.nodes.reduce(function (result, node, index) { + + if (Utils.validateAccessRoles(node[$is.userrole])) { + result.push(node); + var $a = WM.element(''), + $a_caption = WM.element(''), + $li = WM.element('
  • ').data('node-data', node), + $i = WM.element(''), + $badge = WM.element(''), + itemLabel = node[labelField], + itemClass = node[iconField], + itemLink = node[itemField], + itemBadge = node[badgeField], + itemAction = node[actionField], + itemChildren = node[childrenField], + $menu, + routeRegex; + + // menu widget expects data as an array. + // push the current object as an array into the internal array + $is._nodes.push(node[childrenField]); + routeRegex = new RegExp('^(#\/|#)' + $routeParams.name + '$'); + //itemLink can be #/routeName or #routeName + if (itemLink && routeRegex.test(itemLink)) { + $li.addClass('active'); + } + + if (itemChildren && WM.isArray(itemChildren)) { + + $menu = WM.element(''); + + $menu.attr({ + 'caption' : itemLabel, + 'dataset' : 'bind:_nodes['+ index +']', + 'itemlabel' : labelField, + 'itemlink' : itemField, + 'itemaction' : itemAction, + 'itemicon' : iconField, + 'itemchildren': childrenField, + 'type' : 'anchor', + 'iconclass' : itemClass || '', + 'on-select' : '_onMenuItemSelect($event, $item)', + 'autoclose' : $is.autoclose + }); - $li.append($menu); - $el.append($li); - } else { - $i.addClass(itemClass); - $a.append($a_caption.html(itemLabel)).attr('href', itemLink).prepend($i); - if (itemBadge) { - $a.append($badge.html(itemBadge)); + $li.append($menu); + $el.append($li); + } else { + $i.addClass(itemClass); + $a.append($a_caption.html(itemLabel)).prepend($i); + if (itemBadge) { + $a.append($badge.html(itemBadge)); + } + $li.append($a); + $el.append($li); } - $li.append($a); - $el.append($li); } - }); + return result; + + }, []); $compile($el.contents())($is); } @@ -216,13 +227,31 @@ WM.module('wm.layouts.containers') } $el.on('click.on-select', '.app-anchor', function (e) { - var $target = WM.element(this), - $li = $target.closest('.app-nav-item'); + var $target = WM.element(this), + $li = $target.closest('.app-nav-item'), + itemLink, + itemAction; $li.closest('ul.app-nav').children('li.app-nav-item').removeClass('active'); $li.addClass('active'); $rs.$safeApply($is, function () { $is.selecteditem = $li.data('node-data'); Utils.triggerFn($is.onSelect, {'$event': e, $scope: $is, '$item': $is.selecteditem}); + + if ($is.selecteditem) { + itemLink = $is.selecteditem[$is.itemlink] || $is.selecteditem.link; + itemAction = $is.selecteditem[$is.itemaction] || $is.selecteditem.action; + + if (itemAction) { + Utils.evalExp($el.scope(), itemAction).then(function () { + if (itemLink) { + $window.location.href = itemLink; + } + }); + } else if (itemLink) { + //If action is not present and link is there + $window.location.href = itemLink; + } + } }); }); @@ -246,8 +275,9 @@ WM.module('wm.layouts.containers') 'PropertiesFactory', 'WidgetUtilService', 'Utils', + '$routeParams', - function (PropertiesFactory, WidgetUtilService, Utils) { + function (PropertiesFactory, WidgetUtilService, Utils, $routeParams) { 'use strict'; var widgetProps = PropertiesFactory.getPropertiesOf('wm.layouts.navitem', ['wm.base']); @@ -262,6 +292,15 @@ WM.module('wm.layouts.containers') $is.widgetProps = attrs.widgetid ? Utils.getClonedObject(widgetProps) : widgetProps; }, 'post': function ($is, $el, attrs) { + //If nav is not data bound then manually set active to nav item if route param is same as nav item link + var firstChild = $el.children().first(); + + if (firstChild.length && firstChild.hasClass('app-anchor')) { + if ($routeParams.name === (firstChild[0].hash && firstChild[0].hash.substring(2))) { + $el.addClass('active'); + } + } + WidgetUtilService.postWidgetCreate($is, $el, attrs); } } @@ -298,6 +337,8 @@ WM.module('wm.layouts.containers') * This property defines the value to be used as key for the icon from the list of values bound to the nav widget as an array of objects of different values. * @param {string=} itemlabel * This property defines the value to be used as key for the label from the list of values bound to the nav widget as an array of objects of different values. + * @param {string=} itemaction + * This property defines the value to be used as key for the action from the list of values bound to the nav widget as an array of objects of different values. * @param {string=} itemlink * This property defines the value to be used as key for the link from the list of values bound to the nav widget as an array of objects of different values. * @param {string=} itemchildren @@ -326,7 +367,8 @@ WM.module('wm.layouts.containers') { "label": "Home", "icon": "wi wi-home", - "link": "#/home" + "link": "#/home", + "action": "Widgets.empForm.save()" }, { "label": "Dropdown", @@ -344,11 +386,13 @@ WM.module('wm.layouts.containers') { "label": "Others", "icon": "wi wi-shopping-cart", - "link": "http://www.example.com" + "link": "http://www.example.com", + "action": "Widgets.empForm.new()" }, { "label": "Inventory", - "icon": "wi wi-tags" + "icon": "wi wi-tags", + "action": "Widgets.empForm.reset()" } ]; }; diff --git a/src/main/webapp/scripts/modules/layouts/containers/panel/panel.js b/src/main/webapp/scripts/modules/layouts/containers/panel/panel.js index 6c91cbc31..ffadab4f9 100644 --- a/src/main/webapp/scripts/modules/layouts/containers/panel/panel.js +++ b/src/main/webapp/scripts/modules/layouts/containers/panel/panel.js @@ -20,7 +20,7 @@ WM.module('wm.layouts.containers') '' + '
    ' + '{{badgevalue}}' + - '' + + '' + '' + '' + '' + diff --git a/src/main/webapp/scripts/modules/layouts/containers/tabs/tabs.js b/src/main/webapp/scripts/modules/layouts/containers/tabs/tabs.js index cfc1129bb..b3f673830 100644 --- a/src/main/webapp/scripts/modules/layouts/containers/tabs/tabs.js +++ b/src/main/webapp/scripts/modules/layouts/containers/tabs/tabs.js @@ -25,7 +25,8 @@ WM.module('wm.layouts.containers') /* get the properties related to the tabs */ var widgetProps = PropertiesFactory.getPropertiesOf('wm.tabs', ['wm.base']), notifyFor = { - 'tabsposition': true + 'tabsposition' : true, + 'defaultpaneindex': true }; /*Define the property change handler. This function will be triggered when there is a change in the widget property */ @@ -34,6 +35,15 @@ WM.module('wm.layouts.containers') case 'tabsposition': scope.setTabsPosition(newVal); break; + case 'defaultpaneindex': + //If no active tab is set ie.. no isdefaulttab then honor the defaultpaneindex + if (!scope.activeTab) { + scope.activeTab = scope.tabs[newVal || 0]; + } + + scope.selectTab(scope.activeTab, false, true); + + break; } } @@ -162,7 +172,7 @@ WM.module('wm.layouts.containers') */ tabs.forEach(function (tab) { if (!activeTab) { - if (tab.isdefaulttab) { + if (tab.isdefaulttab && !attrs.defaultpaneindex) { activeTab = tab; activeTab.isActive = true; } @@ -171,6 +181,8 @@ WM.module('wm.layouts.containers') } }); + scope.activeTab = activeTab; + /*selects a given tab and executes onBeforeSwitchTab before switching*/ function selectTab(tab, onBeforeSwitchTab) { /*trigger onBeforeSwitchTab callback before switching*/ @@ -181,12 +193,6 @@ WM.module('wm.layouts.containers') } } - /* if isdefaulttab is not set on any of the tabs, then set the first tab as active */ - activeTab = activeTab || tabs[0]; - - if (activeTab) { - scope.selectTab(activeTab, false, true); - } /** * @ngdoc function * @name wm.layouts.containers.directive:wmTabs#next @@ -319,6 +325,7 @@ WM.module('wm.layouts.containers') '
    ' + ' ' + '' + + '{{badgevalue}}' + '
    ' + '' + '', @@ -355,6 +362,9 @@ WM.module('wm.layouts.containers') scope.$lazyLoad = WM.noop; }, 'post': function (scope, element, attrs, ctrl) { + + var parentScope = element.closest('.app-tabs').isolateScope(); + //To support backward compatibility for old projects if (scope.title === undefined && !scope.bindtitle) { scope.title = scope.heading || scope.bindheading; @@ -391,6 +401,11 @@ WM.module('wm.layouts.containers') Utils.triggerFn(WM.element(this).isolateScope().redraw); }); scope.isActive = true; + + if (CONSTANTS.isRunMode && parentScope.onChange) { + parentScope.onChange({'$event': $event, '$scope': parentScope, 'newPaneIndex': scope.tabId, 'oldPaneIndex': parentScope.activeTab.tabId || 0}); + } + ctrl.selectTab(scope); }; @@ -495,7 +510,12 @@ WM.module('wm.layouts.containers') * Width of the tabs widget. * @param {string=} height * Height of the tabs widget. - * @param {boolean=} tabsposition + * @param {number=} defaultpaneindex + * Makes the tab active for given index.This property has backward compatibility for isdefaulttab property.
    + * Default value: 0 + * @param {string=} on-change + * Callback function which will be triggered when the widget value is changed. + * @param {string=} tabsposition * Align the tab headers to left/right/top/bottom of the content.
    * Default value: `top` * @param {string=} transition @@ -608,10 +628,6 @@ WM.module('wm.layouts.containers') * Show is a bindable property.
    * This property will be used to show/hide the tab on the web page.
    * Default value: `true`. - * @param {boolean=} isdefaulttab - * isdefaulttab is a bindable property.
    - * First tab with `isdefaulttab = true` will be displayed by default.
    - * Default value: `false`. * @param {boolean=} show * Show is a bindable property.
    * This property will be used to show/hide the tab on the web page.
    diff --git a/src/main/webapp/scripts/modules/layouts/containers/wizard/wizard.js b/src/main/webapp/scripts/modules/layouts/containers/wizard/wizard.js index 23473a363..fcc08cadb 100644 --- a/src/main/webapp/scripts/modules/layouts/containers/wizard/wizard.js +++ b/src/main/webapp/scripts/modules/layouts/containers/wizard/wizard.js @@ -20,7 +20,7 @@ WM.module('wm.layouts.containers') 'Skip »' + '
    ' + '' + - '' + @@ -28,7 +28,7 @@ WM.module('wm.layouts.containers') '{{nextbtnlabel}}' + '' + '' + - '' + @@ -70,8 +70,10 @@ WM.module('wm.layouts.containers') } function navigateToStep($is, stepIndex) { - $is.currentStep = $is.steps[stepIndex]; - $is.currentStep.status = STEP_STATUS.CURRENT; + if ($is.steps[stepIndex]) { + $is.currentStep = $is.steps[stepIndex]; + $is.currentStep.status = STEP_STATUS.CURRENT; + } } return { @@ -109,18 +111,23 @@ WM.module('wm.layouts.containers') if (CONSTANTS.isRunMode) { //Function to navigate to next step $is.next = function () { - var params = {$isolateScope: $is, currentStep: $is.currentStep, stepIndex: $is.currentStep.stepIndex}; + var params = {$isolateScope: $is, currentStep: $is.currentStep, stepIndex: $is.currentStep.stepIndex}, + stepIndex; if ($is.currentStep.onNext) { if ($is.currentStep.onNext(params) === false) { return; } } $is.currentStep.status = STEP_STATUS.COMPLETED; - navigateToStep($is, $is.currentStep.stepIndex + 1); + stepIndex = $is.currentStep.stepIndex + 1; + stepIndex = $is.steps[stepIndex].show ? stepIndex : stepIndex + 1; + + navigateToStep($is, stepIndex); }; //Function to navigate to previous step $is.prev = function () { - var params; + var params, + stepIndex; if ($is.currentStep.onPrev) { params = {$isolateScope: $is, currentStep: $is.currentStep, stepIndex: $is.currentStep.stepIndex}; if ($is.currentStep.onPrev(params) === false) { @@ -128,7 +135,9 @@ WM.module('wm.layouts.containers') } } $is.currentStep.status = STEP_STATUS.DISABLED; - navigateToStep($is, $is.currentStep.stepIndex - 1); + stepIndex = $is.currentStep.stepIndex - 1; + stepIndex = $is.steps[stepIndex].show ? stepIndex : stepIndex - 1; + navigateToStep($is, stepIndex); }; //Function to skip current step $is.skip = function () { @@ -193,7 +202,7 @@ WM.module('wm.layouts.containers') var widgetProps = PropertiesFactory.getPropertiesOf('wm.wizardstep', ['wm.base']), STEP_STATUS = {'COMPLETED': 'COMPLETED', 'CURRENT': 'CURRENT', 'DISABLED': 'DISABLED'}, - $headerElement = '
  • ' + + $headerElement = '
  • ' + '' + '' + ' ' + @@ -238,6 +247,7 @@ WM.module('wm.layouts.containers') //$watch on step load ie.. step is active and trigger onLoad event $is.$watch('status', function (nv) { if (nv === STEP_STATUS.CURRENT) { + $is.__load(); if (CONSTANTS.isRunMode) { if ($is.onLoad) { $is.onLoad({$isolateScope: $is}); diff --git a/src/main/webapp/scripts/modules/layouts/device/styles/less/mobile.less b/src/main/webapp/scripts/modules/layouts/device/styles/less/mobile.less index 3922ea1bc..f8dbf465d 100644 --- a/src/main/webapp/scripts/modules/layouts/device/styles/less/mobile.less +++ b/src/main/webapp/scripts/modules/layouts/device/styles/less/mobile.less @@ -10,7 +10,7 @@ top: 0; &.slide-over { left: -100%; - z-index: 1049; + z-index: 1050; &.visible { left: 0; @@ -45,6 +45,16 @@ left: 0; height: 100%; width: 100%; + &.left-panel-container:after { + background: rgba(0, 0, 0, 0.5); + position: absolute; + top: 0; + width: 100%; + height: 100%; + content: ""; + display: block; + z-index: 1049; + } &.slide-in-left-panel-container { position: fixed; overflow: visible; @@ -70,7 +80,7 @@ height: @app-mobile-nav-height !important; width: 100%; position: fixed; - z-index: 1060; + z-index: 1049; display: table; top: 0; left: 0; @@ -124,7 +134,7 @@ /*Search widget container (available only in Mobile)*/ .app-search { - z-index: 99999; + z-index: 3000; position: fixed; left: 0; top: @app-mobile-nav-height; @@ -227,9 +237,6 @@ } - } - .modal { - z-index: 999999 !important; } .app-top-nav { color: #999; diff --git a/src/main/webapp/scripts/modules/layouts/page/leftpanel.js b/src/main/webapp/scripts/modules/layouts/page/leftpanel.js index a3710344d..b5c19ddc5 100644 --- a/src/main/webapp/scripts/modules/layouts/page/leftpanel.js +++ b/src/main/webapp/scripts/modules/layouts/page/leftpanel.js @@ -85,6 +85,7 @@ WM.module('wm.layouts.page') element.on(eventName, function () { skipEvent = true; }); + appPage.addClass('left-panel-container'); appPage.on(eventName, function () { if (!skipEvent) { scope.collapse(); @@ -94,6 +95,7 @@ WM.module('wm.layouts.page') }; scope.collapse = function () { var appPage = element.closest('.app-page'); + appPage.removeClass('left-panel-container'); element.removeClass('visible'); element.off(eventName); appPage.off(eventName); diff --git a/src/main/webapp/scripts/modules/layouts/page/page.js b/src/main/webapp/scripts/modules/layouts/page/page.js index 3124e91a6..94c08d3db 100644 --- a/src/main/webapp/scripts/modules/layouts/page/page.js +++ b/src/main/webapp/scripts/modules/layouts/page/page.js @@ -52,7 +52,6 @@ WM.module('wm.layouts.page') count, subView, AppManager; - /* if the page belongs to a prefab use the name of the prefab * else if the project is of prefab type use `Main` * else get the name of the page from the ng-controller attribute @@ -315,15 +314,6 @@ WM.module('wm.layouts.page') } } - /*For popover variables will not be set as init of popover will happen first and then setting variables - so popover calls reset partial variables in case of partial content bind - */ - $rs.$on('reset-partial-variables', function (evt, partialName) { - if (partialName === pageName) { - setVariables($s, containerScope, variableScope, pageName); - } - }); - setVariables($s, containerScope, variableScope, pageName); }, 'post': function ($s, $el, attrs) { @@ -333,7 +323,10 @@ WM.module('wm.layouts.page') if (CONSTANTS.isRunMode) { // register session timeout handler handlers.push($rs.$on('on-sessionTimeout', function () { - Utils.triggerFn($s.onSessionTimeout); + //check if 'onSessionTimeout' event is present in current partial page script + if ($s.hasOwnProperty('onSessionTimeout')) { + Utils.triggerFn($s.onSessionTimeout); + } })); /** diff --git a/src/main/webapp/scripts/modules/layouts/page/pagecontent.js b/src/main/webapp/scripts/modules/layouts/page/pagecontent.js index e3fd68069..1227dcaab 100644 --- a/src/main/webapp/scripts/modules/layouts/page/pagecontent.js +++ b/src/main/webapp/scripts/modules/layouts/page/pagecontent.js @@ -6,8 +6,14 @@ WM.module('wm.layouts.page') $templateCache.put('template/layout/page/pagecontent.html', '
    ' ); + $templateCache.put('template/layout/page/pagecontent-loader.html', + '
    ' + + '
    ' + + '
    ' + + '
    ' + ); }]) - .directive('wmPageContent', ['PropertiesFactory', 'WidgetUtilService', 'CONSTANTS', 'Utils', function (PropertiesFactory, WidgetUtilService, CONSTANTS, Utils) { + .directive('wmPageContent', ['$route', '$rootScope', '$templateCache', '$timeout', 'PropertiesFactory', 'WidgetUtilService', 'CONSTANTS', 'Utils', function ($route, $rootScope, $templateCache, $timeout, PropertiesFactory, WidgetUtilService, CONSTANTS, Utils) { 'use strict'; var widgetProps = PropertiesFactory.getPropertiesOf('wm.layouts.pagecontent', ['wm.layouts', 'wm.base.events.touch']), @@ -25,6 +31,27 @@ WM.module('wm.layouts.page') } } + /*Delays transclusion for the transition and variables to load.*/ + function waitForTransition($ele) { + var iScope = $ele.isolateScope(), + $spinnerEl; + $ele.addClass('load'); + $spinnerEl = WM.element($templateCache.get('template/layout/page/pagecontent-loader.html')); + $spinnerEl.appendTo($ele); + Utils.listenOnce($rootScope, 'page-transition-end', function () { + iScope.__load(); + Utils.triggerFn($ele.scope().onPagePartLoad); + }); + Utils.listenOnce($rootScope, 'page-startupdate-variables-loaded', function () { + $timeout(function () { + $spinnerEl.remove(); + $ele.removeClass('load'); + }, 100); + }); + iScope.loadmode = 'after-select'; + Utils.triggerFn($ele.scope().registerPagePart); + } + return { 'restrict': 'E', 'replace': true, @@ -37,17 +64,8 @@ WM.module('wm.layouts.page') 'pre': function (scope, element) { /*Applying widget properties to directive scope*/ scope.widgetProps = widgetProps; - if (CONSTANTS.isRunMode) { - Utils.triggerFn(element.scope().registerPagePart); - - /** We do not require a delay for page dialogs. Delay is required only for pages*/ - if(!element.closest('.app-dialog').length){ - scope.loadmode = 'after-delay'; - scope.loaddelay = 100; - } - scope.__onTransclude = function () { - Utils.triggerFn(element.scope().onPagePartLoad); - }; + if (CONSTANTS.isRunMode && $route.current && !$route.current.transitionCompleted) { + waitForTransition(element); } }, diff --git a/src/main/webapp/scripts/modules/layouts/styles/less/layouts.less b/src/main/webapp/scripts/modules/layouts/styles/less/layouts.less index ab205a09a..331885a0a 100644 --- a/src/main/webapp/scripts/modules/layouts/styles/less/layouts.less +++ b/src/main/webapp/scripts/modules/layouts/styles/less/layouts.less @@ -84,12 +84,12 @@ body { vertical-align: top; .app-menu .app-button, .panel-action { color: inherit; + outline: none; .app-anchor { color: inherit; text-decoration: none; } a:hover { - color: inherit; text-decoration: none; } background: transparent; @@ -124,7 +124,6 @@ body { min-width: 100px; .panel-help-header { background: @app-panel-help-header-color; - font-weight: bold; border-bottom: 1px @app-panel-border-color solid; padding: 0.5em; margin: 0; @@ -156,7 +155,7 @@ body { } &.fullscreen { position: fixed; - z-index: 10000; + z-index: 1049; left: 0.2em; right: 0.2em; top: 0.2em; @@ -173,6 +172,11 @@ body { } } } +.app-dialog { + .app-panel.fullscreen { + height: 95vh; + } +} .app-panel-footer { position: relative; } @@ -423,6 +427,7 @@ body { #wm-app-content , .app-page, .app-partial, .app-included-page { .width-height(); position: relative; + background-color: inherit; } /*********************************************************************************************************************/ /***********************************overriding the bootstrap styles*************************************************/ @@ -462,6 +467,7 @@ body { } } /*********************application content********************/ + @app-page-content-loader-fg-color: rgba(0,0,0,0.5); .app-content { position: relative; .width-height(); @@ -469,7 +475,7 @@ body { float: none; /**added to support bootstrap col-*-* classes**/ margin-left: auto; margin-right: auto; - background-color: #fff; + background-color: inherit; .app-content-row { .width-height(); position: relative; @@ -497,6 +503,18 @@ body { } } } + .app-page-content { + > .app-ng-transclude { + opacity: 1; + transition: opacity 0.5s ease-in-out; + } + &.load { + overflow: hidden; + > .app-ng-transclude { + opacity: 0; + } + } + } } /*********************application content vertical scroll fix********************/ .app-top-nav + .app-content { @@ -529,6 +547,14 @@ body { text-align: left; } } + .app-left-panel, .app-right-panel { + .nav li { + .badge { + right: 1em; + top: 1em; + } + } + } /*********************application right panel********************/ .app-right-panel { height: 100%; @@ -645,7 +671,7 @@ body { > .panel-body { padding: 4px 4px 0 4px; } - .app-label, .app-anchor { + .app-label { overflow: hidden; text-overflow: ellipsis; word-break: break-word; @@ -654,43 +680,7 @@ body { font-size: 0.8em; padding: 0 1.25em; } - &.align-left .app-label { - text-align: left; - } - &.align-center { - .app-label { - text-align: center; - } - &.position-top { - text-align: center; - .form-control { - text-align: center; - } - } - .form-control-static + .form-control { - width: 100%; - } - .input-group, .app-blob-upload { - display: inline-table; - } - } - &.align-right { - .app-label { - text-align: right; - } - &.position-top { - direction: rtl; - } - } - &.position-left { - direction: ltr; - } - &.position-right { - direction: rtl; - label { - float: right; - } - } + input:focus.ng-invalid { border-color: crimson; } @@ -749,85 +739,107 @@ body { height: 1px; } } - - .app-form { - &.position-top { - .control-label { - display: block; - width: 100%; + /****************Alignment and position classes******************************/ + .app-form, .app-liveform, .app-device-liveform, .app-livefilter { + /****************Caption Align Right******************************/ + &.align-right { + .app-label { + text-align: right; + } + .caption-top { + .form-control, .app-toggle, .app-ratings, .input-group, .app-checkboxset, .app-radioset, .app-checkbox { + float: right; + } + } + .app-checkboxset .app-checkbox, .app-radioset .app-radio { + float: none; + label { + text-align: left; + } } } - } - - .app-liveform-dialog { - .app-dialog-body { - padding: 0; - } - } - /************** Composite Widget - Caption Position Styles ****************/ - .caption-left, .caption-right { - > .app-currency, > .app-colorpicker { - direction: inherit; // To maintain positions of currency ($) on left hand side and text box on right hand side + /****************Caption Align Middle******************************/ + &.align-center { + .app-label { + text-align: center; + } + .caption-top { + text-align: center; + .form-control { + text-align: center; + width: 100%; + } + } + .input-group, .app-blob-upload { + display: inline-table; + } } - } - .caption-left { - > * { - direction: ltr; - float: left; + /************** Fixing widgets when Position Right is applied on the parent ****************/ + + .app-checkboxset, .app-radioset { + margin-bottom:0; + } + /*To remove the animation caused by ng-show/ng-hide*/ + .ng-hide-add, .ng-hide-remove { + transition: 0s linear all; } } - .caption-right { - > * { - direction: rtl; - float: right; - left: auto; // To override col-md-push-2 inherited from bootstrap + .app-liveform-dialog { + .app-dialog-body { + padding: 0; } } - - .caption-top { - > .app-label { + /****************Caption Position top do not remove for old projects******************************/ + [captionposition="top"]{ + .control-label { width: 100%; } } - /************** Composite Widget - Caption Position Styles(to override style inside form) ****************/ - .app-form, .app-liveform, .app-livefilter { - - .caption-left.app-composite-widget, .caption-right.app-composite-widget { - > .app-currency, > .app-colorpicker { - direction: inherit; // To maintain positions of currency ($) on left hand side and text box on right hand side + /*******Caption Styling in the composite widget********/ + .app-composite-widget { + &.caption-right { + .form-control, .input-group, .app-ratings, .app-toggle, .app-switch, .app-fileupload, .app-button-wrapper, .control-label, .app-checkbox, .app-checkboxset, .app-radioset { + float: right; } - } - - - .app-checkboxset, .app-radioset { - margin-bottom:0; - } - - .caption-left.app-composite-widget { - > * { - float: left; + .app-checkboxset .app-checkbox, .app-radioset .app-radio { + float: none; + label { + text-align: left; + } + } + //Datetime, date & time widgets + .app-datetime, .app-date, .app-timeinput { + .dropdown-menu { + left: 0 !important; /*!important is used to overwrite the inline styles*/ + white-space: normal; /*In order to prevent the UI from breaking*/ + } } - } - .caption-right.app-composite-widget { - > * { - float: right; - left: auto; // To override col-md-push-2 inherited from bootstrap + //Tab Widget + .app-tabs { + .nav { + &.nav-tabs { + text-align: right; + } + } } - } - .caption-top.app-composite-widget { - > .app-label { - width: 100%; + .app-slider { + text-align: right; /*IE9 support*/ + .range-input { + margin-top: 0.5em; + } } } - /*To remove the animation caused by ng-show/ng-hide*/ - .ng-hide-add, .ng-hide-remove { - transition: 0s linear all; + &.caption-top { + .control-label { + width:100% + } } } + .app-search { .app-textbox.ng-hide-animate { display: none !important; @@ -872,6 +884,30 @@ body { animation: @name @duration ease-in; } + @-webkit-keyframes toAndFro{ + 0%{ + -webkit-transform : translate3d(0, 0, 0); + } + 50% { + -webkit-transform : translate3d(1000%, 0, 0); + } + 100%{ + -webkit-transform : translate3d(0, 0, 0); + } + } + + @keyframes toAndFro { + from { + transform: translate3d(0, 0, 0); + } + 50% { + transform: translate3d(100vw, 0, 0); + } + to { + transform: translate3d(0, 0, 0); + } + } + .wmScaleInLeft { .animation('wmScaleInLeft', 0.3s); } @@ -1059,6 +1095,19 @@ body { } } + @-webkit-keyframes flipEntry { + 0% { + opacity: 0; + -webkit-transform: perspective(1000px) rotateY(180deg); + } + 50%{opacity: 0;} + 51% {opacity : 1;} + 100% { + opacity : 1; + -webkit-transform: perspective(1000px) rotateY(0deg); + } + } + @keyframes flipEntry { 0% { opacity: 0; @@ -1072,6 +1121,19 @@ body { } } + @-webkit-keyframes flipExit { + 0% { + opacity: 1; + -webkit-transform: perspective(1000px) rotateY(0deg); + } + 50%{opacity: 1;} + 51% {opacity : 0;} + 100% { + opacity : 0; + -webkit-transform: perspective(1000px) rotateY(-180deg); + } + } + @keyframes flipExit { 0% { opacity: 1; @@ -1085,6 +1147,18 @@ body { } } + @-webkit-keyframes flipReverseEntry { + 0% { + opacity: 0; + -webkit-transform: perspective(1000px) rotateY(-180deg); + } + 50%{opacity: 0;} + 51% {opacity : 1;} + 100% { + opacity : 1; + -webkit-transform: perspective(1000px) rotateY(0deg); + } + } @keyframes flipReverseEntry { 0% { opacity: 0; @@ -1098,6 +1172,19 @@ body { } } + @-webkit-keyframes flipReverseExit { + 0% { + opacity: 1; + -webkit-transform: perspective(1000px) rotateY(0deg); + } + 50%{opacity: 1;} + 51% {opacity : 0;} + 100% { + opacity : 0; + -webkit-transform: perspective(1000px) rotateY(180deg); + } + } + @keyframes flipReverseExit { 0% { opacity: 1; @@ -1112,79 +1199,95 @@ body { } #wm-app-content { - +.ng-enter, &.ng-leave{ - position: fixed; - top: 0; - left: 0; - -webkit-backface-visibility: visible !important; - -ms-backface-visibility: visible !important; - backface-visibility: visible !important; - animation-duration: 0.5s; - animation-timing-function: linear; - -webkit-animation-fill-mode: forwards; - animation-fill-mode: forwards; - - > .app-page >.app-left-panel { + >.page-transition { + &.app-page, +.app-page { + position: fixed; + top: 0; + left: 0; + -webkit-backface-visibility: visible !important; + -ms-backface-visibility: visible !important; + -webkit-backface-visibility: visible !important; + backface-visibility: visible !important; + -webkit-animation-duration: 0.3s; + animation-duration: 0.3s; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + -webkit-animation-fill-mode: forwards; + animation-fill-mode: forwards; + } + > .app-page > .app-left-panel { display: none; } } - &.page-transition-slide { - &.ng-leave { + >.page-transition-slide { + &.ng-leave-active { + -webkit-animation-name: slideOutLeft; animation-name: slideOutLeft; } - +.ng-enter { + +.ng-enter-active { + -webkit-animation-name: slideInRight; animation-name: slideInRight; } } - &.page-transition-slide-exit { - &.ng-leave { + >.page-transition-slide-exit { + &.ng-leave-active { + -webkit-animation-name: slideOutRight; animation-name: slideOutRight; } - +.ng-enter { + +.ng-enter-active { + -webkit-animation-name: slideInLeft; animation-name: slideInLeft; } } /** popup **/ - &.page-transition-pop +.ng-enter { + >.page-transition-pop +.ng-enter { + -webkit-animation-name: zoomIn; animation-name: zoomIn ; } - &.page-transition-pop-exit { - &.ng-leave { + >.page-transition-pop-exit { + &.ng-leave-active { z-index: 1; + -webkit-animation-name: zoomOut; animation-name: zoomOut; } - &.ng-enter { + &.ng-enter-active { z-index: 0; } } - &.page-transition-flip { - &.ng-leave { + >.page-transition-flip { + &.ng-leave-active { + -webkit-animation-name: flipExit; animation-name: flipExit; } - +.ng-enter { + +.ng-enter-active { + -webkit-animation-name: flipEntry; animation-name: flipEntry; } } - &.page-transition-flip-exit { - &.ng-leave { + >.page-transition-flip-exit { + &.ng-leave-active { + -webkit-animation-name: flipReverseExit; animation-name: flipReverseExit; } - +.ng-enter { + +.ng-enter-active { + -webkit-animation-name: flipReverseEntry; animation-name: flipReverseEntry; } } /** fade **/ - &.page-transition-fade +.ng-enter { + >.page-transition-fade +.ng-enter-active { + -webkit-animation-name: fadeIn; animation-name: fadeIn; } - &.page-transition-fade-exit.ng-leave { + >.page-transition-fade-exit.ng-leave-active { + -webkit-animation-name: fadeOut; animation-name: fadeOut; z-index: 1; } @@ -1199,6 +1302,9 @@ body { .item{ overflow : hidden; height : 100%; + img { + width: 100%; + } } } &.carousel { @@ -1359,7 +1465,7 @@ body { padding: 0; text-align: center; &:hover { - color: initial; + color: inherit; } } .btn { @@ -1377,7 +1483,6 @@ body { width: 42pt; box-shadow: rgba(0, 0, 0, 0.117647) 0 1px 2px 0, rgba(0, 0, 0, 0.239216) 0 1px 1px 0; border-radius: 500px; - overflow: hidden; padding: 0; } &.btn-raised { @@ -1399,11 +1504,6 @@ body { > .btn-caption, > .anchor-caption { display: block; } - .badge { - top: -10pt; - right: -10pt; - font-weight: normal; - } } /********for buttons, anchors with icon on right****/ .btn[icon-position = "right"], a[icon-position = "right"] { @@ -1411,8 +1511,10 @@ body { } /****Badge****/ .btn .badge, a .badge { - right: -10px; - top: -10px; + right: -1em; + top: -1em; + font-weight: normal; + z-index: 3; &.btn-link { top: 0; right: 0; @@ -1441,86 +1543,8 @@ body { margin-top: 0; } - /************** Fixing widgets when Position Right is applied on the parent ****************/ - .position-right { - //Switch widget - .app-switch { - float: right; - .btn-group { - direction: ltr; - } - } - - .app-search { - &.input-group { - .form-control { - float: right; - } - } - } - - //Common styles for Radio, Checkbox, Radioset & Checkboxset widgets - .app-radioset, .app-checkboxset { - direction: ltr; - } - - .radio, .checkbox, .radio-inline, .checkbox-inline { - label { - text-align: right; - } - - .caption { - margin-right: 1.5em; - } - - input[type="radio"], input[type="checkbox"] { - right: 0; - } - - //Reset position from left hand side - &.col-md-push-3 { - left: 0; - } - } - //Datetime, date & time widgets - .app-datetime, .app-date, .app-timeinput { - .dropdown-menu { - left: 0 !important; /*!important is used to overwrite the inline styles*/ - white-space: normal; /*In order to prevent the UI from breaking*/ - } - } - - //Tab Widget - .app-tabs { - .nav { - &.nav-tabs { - text-align: right; - } - } - } - - .app-toggle { - float: right; - } - - .app-slider { - text-align: right; /*IE9 support*/ - .range-input { - margin-top: 0.5em; - } - } - - .app-fileupload { - float: right; - - .app-button-wrapper { - float: right; - } - } - } .p { width: 100%; - color: inherit; word-wrap: break-word; } .h1,.h2,.h3,.h4,.h5,.h6 { @@ -1529,20 +1553,23 @@ body { .inline { display: inline; } + .bold { + font-weight: bold; + } .bordered { - border: 1px solid rgb(0,0,0,0.2); + border: 1px solid rgba(0,0,0,0.2); } .bordered-left { - border-left: 1px solid rgb(0,0,0,0.2); + border-left: 1px solid rgba(0,0,0,0.2); } .bordered-right { - border-right: 1px solid rgb(0,0,0,0.2); + border-right: 1px solid rgba(0,0,0,0.2); } .bordered-top { - border-top: 1px solid rgb(0,0,0,0.2); + border-top: 1px solid rgba(0,0,0,0.2); } .bordered-bottom { - border-bottom: 1px solid rgb(0,0,0,0.2); + border-bottom: 1px solid rgba(0,0,0,0.2); } .vertical-align-top { vertical-align: top; @@ -1570,6 +1597,12 @@ body { overflow: inherit; } } + .panel-heading { + .description:not(:empty) { + font-size: .75em; + margin-top: 5px; + } + } //Angular UI bootstrap 1.1.0 related styles. .ng-animate.item:not(.left):not(.right){-webkit-transition:0s ease-in-out left;transition:0s ease-in-out left}; .uib-datepicker .uib-title{width:100%;} diff --git a/src/main/webapp/scripts/modules/layouts/styles/less/mobile-layouts.less b/src/main/webapp/scripts/modules/layouts/styles/less/mobile-layouts.less index ca2968abb..4355bd121 100644 --- a/src/main/webapp/scripts/modules/layouts/styles/less/mobile-layouts.less +++ b/src/main/webapp/scripts/modules/layouts/styles/less/mobile-layouts.less @@ -7,6 +7,10 @@ @app-mobile-tabbar-active-item-bg-color: rgba(0,0,0,0.1); @app-mobile-nav-searchinput-border: #fff; @app-mobile-nav-button-padding: (@app-mobile-nav-height - @app-mobile-nav-font-size)/2; + /*****Removing outline*****/ + * { + outline: none !important; + } /******************************************************************************** *************************************** Mobile Left Panel *********************** *********************************************************************************/ @@ -160,7 +164,6 @@ } .navbar { - background-color: inherit; border: 0; margin: 0; min-height: 0; @@ -190,21 +193,6 @@ } .navbar-nav { margin: auto; - background-color: inherit; - > li { - height: @app-mobile-nav-height; - > .app-menu, > .app-popover { - > a { - line-height: @app-mobile-nav-height; - text-decoration: none; - background-color: transparent; - color: inherit; - } - .caret { - display: none; - } - } - } .btn-back > * { vertical-align: middle; } @@ -214,22 +202,19 @@ &.navbar-right { text-align: right; .dropdown { - background-color: inherit; > .app-button, > a { - background-color: transparent; font-size: inherit; padding: 0 5pt; line-height: @app-mobile-nav-height; - color: inherit; border: 0; } .dropdown-menu { position: absolute; - background-color: inherit; right: 0; left: auto; + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + background-color: #fff; > li > a { - color: inherit; &:hover { background-color: rgba(0,0,0,0.1); } @@ -240,8 +225,18 @@ > li { display: inline-block; vertical-align: top; - background-color: inherit; white-space: nowrap; + height: @app-mobile-nav-height; + > .app-menu, > .app-popover { + > a { + line-height: @app-mobile-nav-height; + text-decoration: none; + color: inherit; + } + .caret { + display: none; + } + } i { font-size: @app-mobile-nav-icon-size; vertical-align: middle; diff --git a/src/main/webapp/scripts/modules/mobile/common/services/deviceService.js b/src/main/webapp/scripts/modules/mobile/common/services/deviceService.js index 693843b27..9a13c01a6 100644 --- a/src/main/webapp/scripts/modules/mobile/common/services/deviceService.js +++ b/src/main/webapp/scripts/modules/mobile/common/services/deviceService.js @@ -8,15 +8,18 @@ * The 'wm.modules.wmCommon.services.$DeviceService' provides high-level API to interact with device. */ wm.modules.wmCommon.services.DeviceService = [ + '$document', '$q', + '$rootScope', 'Utils', //This is required for initialization 'OfflineSecurityService', - function ($q, Utils) { + function ($document, $q, $rootScope, Utils) { 'use strict'; var isDeviceReady = true, isDeviceReadyEventListeners = [], + backBtnTapListeners = [], waitingFor = {}; function triggerListeners(listeners) { @@ -69,4 +72,35 @@ wm.modules.wmCommon.services.DeviceService = [ } return d.promise; }; + + $document.on('backbutton', function () { + _.forEach(backBtnTapListeners, function (fn) { + return !(fn() === false); + }); + $rootScope.$safeApply($rootScope); + }); + /** + * + * @ngdoc method + * @name wm.modules.wmCommon.services.$DeviceService#onBackButtonTap + * @methodOf wm.modules.wmCommon.services.$DeviceService + * @description + * When back button on android devices is tapped, then this function will invoke the given callback. The + * registered callbacks are invoked in reverse chronological order. A callback can stop propagation by + * returning boolean false. + * + * @param {Function} fn callback function to invoke. + * @returns {Function} a function to call to deregister + */ + this.onBackButtonTap = function (fn) { + backBtnTapListeners.splice(0, 0, fn); + return function () { + var i = _.findIndex(backBtnTapListeners, function (v) { + return v === fn; + }); + if (i >= 0) { + backBtnTapListeners.splice(i, 1); + } + }; + }; }]; \ No newline at end of file diff --git a/src/main/webapp/scripts/modules/mobile/layouts/containers/navbar/navbar.js b/src/main/webapp/scripts/modules/mobile/layouts/containers/navbar/navbar.js index 967ba7058..c2dde44b0 100644 --- a/src/main/webapp/scripts/modules/mobile/layouts/containers/navbar/navbar.js +++ b/src/main/webapp/scripts/modules/mobile/layouts/containers/navbar/navbar.js @@ -52,7 +52,7 @@ WM.module('wm.layouts.containers') '' + '
  • ' + '
    ' + - '' + + '' + '
    ' + '
    ' + '
    ' ); }]) - .directive('wmMobileFileBrowser', [ '$templateCache', 'CONSTANTS', function ($templateCache, CONSTANTS) { + .directive('wmMobileFileBrowser', [ '$templateCache', 'CONSTANTS', 'DeviceService', function ($templateCache, CONSTANTS, DeviceService) { 'use strict'; function loadFileSize(files, onComplete, index) { index = index || 0; @@ -62,11 +62,21 @@ WM.module('wm.widgets.advanced') 'template' : $templateCache.get('template/widget/advanced/mobileFileBrowser.html'), 'scope' : {'onSelect' : '&'}, 'link' : function (scope) { + var backButtonListenerDeregister; if (CONSTANTS.isStudioMode) { return; } + backButtonListenerDeregister = DeviceService.onBackButtonTap(function () { + if (scope.show) { + scope.show = false; + return false; + } + }); + scope.$on('$destroy', function () { + backButtonListenerDeregister(); + }); scope.selectedFiles = []; scope.directory = undefined; scope.getFileExtension = function (fileName) { diff --git a/src/main/webapp/scripts/modules/mobile/wmMobile.js b/src/main/webapp/scripts/modules/mobile/wmMobile.js index dca9aa2ed..eb34c6561 100644 --- a/src/main/webapp/scripts/modules/mobile/wmMobile.js +++ b/src/main/webapp/scripts/modules/mobile/wmMobile.js @@ -1,4 +1,4 @@ -/*global WM, window, _, cordova, document*/ +/*global WM, window, _, cordova, document, navigator */ WM.module('wm.mobile', ['wm.variables', 'wm.layouts', 'wm.widgets', 'ngCordova', 'ngCordovaOauth', 'wm.plugins.offline']) //Initialize project @@ -7,24 +7,37 @@ WM.module('wm.mobile', ['wm.variables', 'wm.layouts', 'wm.widgets', 'ngCordova', 'DeviceFileService', 'DeviceFileCacheService', function ($rootScope, $location, CONSTANTS, AppAutoUpdateService) { 'use strict'; + + var initialScreenSize, + $appEl, + pageReadyDeregister; + /* Mark the mobileApplication type to true */ $rootScope.isMobileApplicationType = true; if ($location.protocol() === 'file') { CONSTANTS.hasCordova = true; + $appEl = WM.element('.wm-app:first'); + initialScreenSize = window.innerHeight; + + $appEl.addClass('cordova'); + + // keyboard class is added when keyboard is open. + window.addEventListener('resize', function () { + if (window.innerHeight < initialScreenSize) { + $appEl.addClass('keyboard'); + } else { + $appEl.removeClass('keyboard'); + } + }); + $rootScope.$on('application-ready', function () { AppAutoUpdateService.start(); }); - } - if (CONSTANTS.isRunMode) { - $rootScope.$on('$routeChangeStart', function () { - WM.element('body >.app-spinner:first').removeClass('ng-hide'); - }); - $rootScope.$on('page-ready', function () { - WM.element('body >.app-spinner:first').addClass('ng-hide'); - }); - $rootScope.$on('template-ready', function () { - WM.element('body >.app-spinner:first').addClass('ng-hide'); + + pageReadyDeregister = $rootScope.$on('page-ready', function () { + navigator.splashscreen.hide(); + pageReadyDeregister(); }); } }]) @@ -87,7 +100,7 @@ WM.module('wm.mobile', ['wm.variables', 'wm.layouts', 'wm.widgets', 'ngCordova', }; //On Application start $rootScope.$on('application-ready', function () { - var msgContent = {key: 'on-load'}; + var msgContent = {key: 'on-load'}; //Notify preview window that application is ready. Otherwise, identify the OS. if (window.top !== window) { window.top.postMessage(Utils.isIE9() ? JSON.stringify(msgContent) : msgContent, '*'); @@ -98,4 +111,4 @@ WM.module('wm.mobile', ['wm.variables', 'wm.layouts', 'wm.widgets', 'ngCordova', } }); } - }]); + }]); \ No newline at end of file diff --git a/src/main/webapp/scripts/modules/plugins/database/application/services/databaseServices.js b/src/main/webapp/scripts/modules/plugins/database/application/services/databaseServices.js index f16060d79..3afc7dffb 100644 --- a/src/main/webapp/scripts/modules/plugins/database/application/services/databaseServices.js +++ b/src/main/webapp/scripts/modules/plugins/database/application/services/databaseServices.js @@ -43,6 +43,7 @@ * - {@link wm.database.$DatabaseService#methods_deleteQuery deleteQuery} * - {@link wm.database.$DatabaseService#methods_validateQuery validateQuery} * - {@link wm.database.$DatabaseService#methods_testRunQuery testRunQuery} + * - {@link wm.database.$DatabaseService#methods_nativeTestRunQuery nativeTestRunQuery} * - {@link wm.database.$DatabaseService#methods_readTableData readTableData} * - {@link wm.database.$DatabaseService#methods_insertTableData insertTableData} * - {@link wm.database.$DatabaseService#methods_updateTableData updateTableData} @@ -1649,7 +1650,7 @@ wm.plugins.database.services.DatabaseService = [ */ testRunProcedure: function (params, successCallback, failureCallback) { - return initiateAction("testRunProcedure", params, successCallback, failureCallback); + return initiateAction("testRunProcedure", params, successCallback, failureCallback, true); }, /** * Internal function @@ -2187,6 +2188,9 @@ wm.plugins.database.services.DatabaseService = [ }, data: params.data }); + }, + executeAggregateQuery: function (params, successCallback, failureCallback) { + return initiateAction("executeAggregateQuery", params, successCallback, failureCallback); } }; } diff --git a/src/main/webapp/scripts/modules/plugins/database/config.js b/src/main/webapp/scripts/modules/plugins/database/config.js index c3f6d40ec..d8446410f 100644 --- a/src/main/webapp/scripts/modules/plugins/database/config.js +++ b/src/main/webapp/scripts/modules/plugins/database/config.js @@ -290,6 +290,10 @@ wm.plugins.database.constant('DB_SERVICE_URLS', { url: "/:service/:dataModelName/queries/execute?page=:page&size=:size", method: "POST" }, + executeAggregateQuery: { + url: "/services/:dataModelName/:entityName/aggregations?page=:page&size=:size&sort=:sort", + method: "POST" + }, testRunQuery: { url: "/:service/:dataModelName/queries/test_run", method: "POST" @@ -306,7 +310,7 @@ wm.plugins.database.constant('DB_SERVICE_URLS', { method: "GET" }, testRunProcedure: { - url: "/:service/:dataModelName/procedures/test_run", + url: "services/projects/:projectID/database/services/:dataModelName/procedures/testrun", method: "POST" }, proceduresInDatabase: { @@ -655,6 +659,11 @@ wm.plugins.database.constant('DB_CONSTANTS', { "label": "CURRENT_TIME", "value": "TIME" }, + "CURRENT_DATE_TIME": { + "property": "Current DateTime", + "label": "CURRENT_DATE_TIME", + "value": "DATETIME" + }, "CURRENT_USER_ID": { "property": "LoggedIn UserId", "label": "CURRENT_USER_ID", diff --git a/src/main/webapp/scripts/modules/plugins/security/application/services/securityservices.js b/src/main/webapp/scripts/modules/plugins/security/application/services/securityservices.js index 59209c238..3087ff89a 100644 --- a/src/main/webapp/scripts/modules/plugins/security/application/services/securityservices.js +++ b/src/main/webapp/scripts/modules/plugins/security/application/services/securityservices.js @@ -622,23 +622,16 @@ wm.plugins.security.services.SecurityService = [ /** * @ngdoc function - * @name wm.security.$SecurityService#getCASOptions + * @name wm.security.$SecurityService#getSAMLOptions * @methodOf wm.security.$SecurityService * @function * * @description - * The API is used to get the values of configured CAS (Central Authentication Service) security provider. - * This API returns appropriate values on the basis of what is set earlier using configureCAS API. + * The API is used to get the values of configured SAML security provider. + * This API returns appropriate values on the basis of what is set earlier using configureSAML API. * - * Following are the fields which need to be set for using CAS: - * a) casURL - A CAS url - * b) projectURL - projects url. - * c) userDetailsProvider - A string which contains the value “CAS”. - * d) DatabaseOptions - DatabaseOptions object * * @param {string} projectID project id - * @param {function} successCallback to be called on success - * @param {function} failureCallback to be called on failure */ getSAMLOptions: function (projectID) { @@ -658,16 +651,23 @@ wm.plugins.security.services.SecurityService = [ * @function * * @description - * The URL configures the SAML as the service provider. This mechanism enables the - * Configuration parameters should be sent through json object using RequestBody. - * Following 3 data members value to be set for using SAML: - * e) samlURL - A CAS url. - * f) projectURL - project url. - * g) userDetailsProvider - A string which contains the value “SAML”. + * Save SAML configurations. + * While calling this API, it is mandatory to set the requestBody. + * Following is the structure of SAMLOptions: + * 1. samlOptions - this property has all the saml related configuration. + * a. createKeystore - (boolean) to auto generate key-store. + * b. idpEndpointUrl - Identity Provider endpoint url. + * c. idpMetadataUrl - Identity Provider metadata url. + * d. idpPublicKey - Identity Provider public key. + * e. keyAlias - Alias key. + * f. keyStoreLocation - Path where the key-store is placed in the project. + * g. keyStoreName - Name of the key-store. + * h. keyStorePassword - Key-store password. + * i. roleMappingEnabled - (boolean) whether the role is mapped. + * j. subjectName - subject name for key-store. + * 2. generalOptions - It consist of 5 fields viz., enforceSecurity, enforceIndexHtml, useSSL, sslPort and dataSourceType respectively. To configure SAML, dataSourceType must be set to “SAML” and enforceSecurity must be true. Other options must be set as per the requirement. * * @param {object} params object containing parameters for the request - * @param {function} successCallback to be called on success - * @param {function} failureCallback to be called on failure */ configSAML: function (params) { @@ -683,27 +683,24 @@ wm.plugins.security.services.SecurityService = [ /** * @ngdoc function - * @name wm.security.$SecurityService#configSAML + * @name wm.security.$SecurityService#loadIdpMetadata * @methodOf wm.security.$SecurityService * @function * * @description - * The URL configures the SAML as the service provider. This mechanism enables the - * Configuration parameters should be sent through json object using RequestBody. - * Following 3 data members value to be set for using SAML: - * e) samlURL - A CAS url. - * f) projectURL - project url. - * g) userDetailsProvider - A string which contains the value “SAML”. + * Loads SAML metadata through MetadataUrl. + * + * Url Parameters + * 1) Project ID + * 2) IDP Metadata URL * * @param {object} params object containing parameters for the request - * @param {function} successCallback to be called on success - * @param {function} failureCallback to be called on failure */ - loadIdpMatadata: function (params) { + loadIdpMetadata: function (params) { return BaseService.execute({ target: 'Security', - action: 'loadIdpMatadata', + action: 'loadIdpMetadata', urlParams: { projectID: params.projectID, idpMetadataUrl: params.idpMetadataUrl @@ -711,6 +708,32 @@ wm.plugins.security.services.SecurityService = [ }); }, + /** + * @ngdoc function + * @name wm.security.$SecurityService#uploadIdpMetadata + * @methodOf wm.security.$SecurityService + * @function + * + * @description + * The uploaded xml file configures SAML as the service provider. + * Configuration parameters should be sent through json object using RequestBody. + * + * This call is a multipart data request. Metadata file should be sent in the request body. + * + * @param {object} params object containing parameters for the request + */ + + uploadIdpMetadata: function (params) { + return BaseService.execute({ + target: 'Security', + action: 'uploadIdpMetadata', + urlParams: { + projectID: params.projectID + }, + data: params.content + }); + }, + /** * @ngdoc function * @name wm.security.$SecurityService#configCustomAuth @@ -961,6 +984,32 @@ wm.plugins.security.services.SecurityService = [ Utils.triggerFn(successCallback, response); }, failureCallback); }, + + /** + * @ngdoc function + * @name wm.security.$SecurityService#generateConfig + * @methodOf wm.security.$SecurityService + * @function + * + * @description + * The API is used to re-generate provider xml file + * + * @param {object} params object containing parameters for the request + * @param {function} successCallback to be called on success + * @param {function} failureCallback to be called on failure + */ + + generateConfig: function (params, successCallback, failureCallback) { + BaseService.send({ + target: 'Security', + action: 'generateConfig', + urlParams: { + projectID: params.projectID + } + }, function (response) { + Utils.triggerFn(successCallback, response); + }, failureCallback); + }, /** * @ngdoc function * @name wm.security.$SecurityService#setRoles diff --git a/src/main/webapp/scripts/modules/plugins/security/config.js b/src/main/webapp/scripts/modules/plugins/security/config.js index 8591aa03c..8f116143f 100644 --- a/src/main/webapp/scripts/modules/plugins/security/config.js +++ b/src/main/webapp/scripts/modules/plugins/security/config.js @@ -80,9 +80,16 @@ wm.plugins.security.constant('SECURITY_URLS', { url: "services/projects/:projectID/securityservice/providers/saml", method: "POST" }, - loadIdpMatadata: { + loadIdpMetadata: { url: "services/projects/:projectID/securityservice/providers/saml/loadidpmetadata?idpMetadataUrl=:idpMetadataUrl", - method: "get" + method: "GET" + }, + uploadIdpMetadata: { + url: "services/projects/:projectID/securityservice/providers/saml/loadidpmetadata", + method: "POST", + headers: { + 'Content-Type': undefined + } }, configCustomAuth: { url: "services/projects/:projectID/securityservice/providers/customauth", @@ -120,6 +127,10 @@ wm.plugins.security.constant('SECURITY_URLS', { url: "services/projects/:projectID/securityservice/rolesconfig", method: "POST" }, + generateConfig: { + url: "services/projects/:projectID/securityservice/generateconfig", + method: "POST" + }, appLogin: { url: "j_spring_security_check", method: "POST", diff --git a/src/main/webapp/scripts/modules/plugins/webservice/application/factories/servicefactory.js b/src/main/webapp/scripts/modules/plugins/webservice/application/factories/servicefactory.js index 491b28980..2962798c2 100644 --- a/src/main/webapp/scripts/modules/plugins/webservice/application/factories/servicefactory.js +++ b/src/main/webapp/scripts/modules/plugins/webservice/application/factories/servicefactory.js @@ -54,6 +54,14 @@ wm.plugins.webServices.factories.ServiceFactory = [ return (operationId ? (_.find(serviceObj.operations, {'operationId' : operationId})) : serviceObj.operations[0]) || {}; }, + /** + * resets the cached operation info for a service + * @param serviceName + */ + resetServiceOperations = function (serviceName) { + getServiceObjectByName(serviceName).operations.length = 0; + }, + /*function to get list of services from the backend*/ getServicesWithType = function (successCallBack, reloadFlag) { /*sanity checking of the params*/ @@ -128,6 +136,14 @@ wm.plugins.webServices.factories.ServiceFactory = [ return; } + /* + * TODO [VIBHU]: doing this to clear previous operation info if cached. + * the getServiceOperations method needs to be merged with this method for consistency + */ + if (forceReload) { + resetServiceOperations(serviceId); + } + /*invoking a service to get the operations that a particular service has and it's * parameters to create a unique url pattern*/ WebService.retrieveServiceOperations(urlParams, function (response) { @@ -199,6 +215,26 @@ wm.plugins.webServices.factories.ServiceFactory = [ return (VARIABLE_CONSTANTS.REST_SUPPORTED_SERVICES.indexOf(type) !== -1);// || VARIABLE_CONSTANTS.SERVICE_TYPE_DATA === type); }, + //Check if variable/operation is a query type and of put/post type + isBodyTypeQueryProcedure = function (variable) { + return (_.includes(['QueryExecution', 'ProcedureExecution'], variable.controller)) && (_.includes(['put', 'post'], variable.operationType)); + }, + + //Return params from swagger for post/put query types + getRawParams = function (operationObj, definitions) { + var refValue = _.get(operationObj, ['parameters', 0, 'schema', '$ref']), + refKey = _.last(_.split(refValue, '/')), + defObj = definitions[refKey], + operationList = []; + _.forEach(defObj.properties, function (value, key) { + value.name = key; + value[parameterTypeKey] = VARIABLE_CONSTANTS.BODY_FIELD; + value.required = _.includes(defObj.required, key); + operationList.push(value); + }); + return operationList; + }, + processOperations = function (serviceObj, operations, swagger) { var paramsKey, isRestSupportedService = isRESTSupported(serviceObj.type), @@ -234,8 +270,7 @@ wm.plugins.webServices.factories.ServiceFactory = [ returnObj, returnType, returnFormat, - dbOperationName, - tag, + rawParameters, isDbServiceOp = function (type) { return type === "hqlquery" || type === "nativequery" || type === "procedure"; }; @@ -243,9 +278,6 @@ wm.plugins.webServices.factories.ServiceFactory = [ if (isDbServiceOp(operation.operationType)) { returnType = operation.return; } else { - dbOperationName = operation.relativePath && operation.relativePath.split("/").pop(); - tag = _.get(operation, 'tags[0]'); - // fetch return type and operation object from swagger if (operation.responses && operation.responses['200'].schema) { schemaObject = operation.responses['200'].schema; @@ -297,7 +329,13 @@ wm.plugins.webServices.factories.ServiceFactory = [ /* process the operation params as well */ if (!WM.element.isEmptyObject(operation[paramsKey])) { operationObject.parameter = []; - WM.forEach(operation[paramsKey], function (param) { + //For post/put query methods get params from definitions + if (isBodyTypeQueryProcedure(operationObject)) { + rawParameters = getRawParams(operation, definitions); + } else { + rawParameters = operation[paramsKey]; + } + WM.forEach(rawParameters, function (param) { isList = param[IS_LIST_KEY]; /* special cases for MultiPart type params */ @@ -315,6 +353,7 @@ wm.plugins.webServices.factories.ServiceFactory = [ if (param.type === "array") { isList = true; typeRef = param.items && param.items.type; + format = param.items && param.items.format; } else { typeRef = param.type; format = param.format; @@ -340,6 +379,8 @@ wm.plugins.webServices.factories.ServiceFactory = [ parameterType: param[parameterTypeKey] }); }); + } else { + operationObject.parameter = []; } /* push an extra RequestBody param for WebSocketService */ @@ -434,9 +475,9 @@ wm.plugins.webServices.factories.ServiceFactory = [ path[operation].relativePath = path['x-WM-RELATIVE_PATH']; /* set operationType for Query/Procedure operations */ - if (path[operation].tags && path[operation].tags[0] === "ProcedureExecutionController") { + if (path[operation].tags && path[operation].tags[0] === WS_CONSTANTS.CONTROLLER_NAMES.PROCEDURE_CONTROLLER) { path[operation].serviceSubType = "procedure"; - } else if (path[operation].tags && path[operation].tags[0] === "QueryExecutionController") { + } else if (path[operation].tags && path[operation].tags[0] === WS_CONSTANTS.CONTROLLER_NAMES.QUERY_CONTROLLER) { path[operation].serviceSubType = "query"; /* here we have to set operationType to either hqlquery or nativequery (have to check how)*/ } @@ -696,7 +737,8 @@ wm.plugins.webServices.factories.ServiceFactory = [ * @param {prefabName} prefab name * @param {object} having service output */ - getPrefabTypes: getPrefabTypes + getPrefabTypes: getPrefabTypes, + isBodyTypeQueryProcedure: isBodyTypeQueryProcedure }; } ]; diff --git a/src/main/webapp/scripts/modules/plugins/webservice/application/services/webServices.js b/src/main/webapp/scripts/modules/plugins/webservice/application/services/webServices.js index 5312cb6db..e9cd6eae4 100644 --- a/src/main/webapp/scripts/modules/plugins/webservice/application/services/webServices.js +++ b/src/main/webapp/scripts/modules/plugins/webservice/application/services/webServices.js @@ -559,7 +559,8 @@ wm.plugins.webServices.services.WebService = function (BaseService) { }, data: params.dataParams || undefined, "isDirectCall": params.isDirectCall, - "byPassResult": true + "byPassResult": true, + "isExtURL": params.isExtURL }, successCallback, failureCallback); }, /** diff --git a/src/main/webapp/scripts/modules/plugins/webservice/config.js b/src/main/webapp/scripts/modules/plugins/webservice/config.js index 995aae94a..9115a6fef 100644 --- a/src/main/webapp/scripts/modules/plugins/webservice/config.js +++ b/src/main/webapp/scripts/modules/plugins/webservice/config.js @@ -148,6 +148,10 @@ wm.plugins.webServices.constant('WS_CONSTANTS', { MULTIPART_FORMDATA: "multipart/form-data", OCTET_STREAM: "application/octet-stream" }, + CONTROLLER_NAMES: { + QUERY_CONTROLLER: "QueryExecutionController", + PROCEDURE_CONTROLLER: "ProcedureExecutionController" + }, HTTP_STATUS_CODE: { CORS_FAILURE: -1 } diff --git a/src/main/webapp/scripts/modules/variables/application/base/basefactory.js b/src/main/webapp/scripts/modules/variables/application/base/basefactory.js index d4ecb9761..d804210c5 100644 --- a/src/main/webapp/scripts/modules/variables/application/base/basefactory.js +++ b/src/main/webapp/scripts/modules/variables/application/base/basefactory.js @@ -109,7 +109,7 @@ wm.variables.factories.BaseVariablePropertyFactory = [ "dataBinding": {"value": ""}, "startUpdate": {"hide": true, "value": ""}, "autoUpdate": {"hide": true, "value": ""}, - "redirectTo": {"type": "list", "options": [], value: "", "widgettype": "typeahead"}, + "redirectTo": {"type": "list", "options": [], value: "", "placeholder": "Search Redirect To", "widgettype": "typeahead"}, "useDefaultSuccessHandler": {"type": "boolean", "widgettype": "boolean-inputfirst", "value": true} }, "wm.NavigationVariable": { @@ -117,7 +117,7 @@ wm.variables.factories.BaseVariablePropertyFactory = [ "owner": {"type": "list", "options": {"Page": "LABEL_PAGE", "App": "LABEL_APPLICATION"}, "value": "Page"}, "operation": {"type": "list", "required": true, "options": {"goToPreviousPage": "goToPreviousPage", "gotoPage": "gotoPage", "gotoTab": "gotoTab", "gotoAccordion": "gotoAccordion"}, "value": "gotoPage"}, "dataBinding": {"type": "string", "value": [], "hide": true}, - "pageTransitions": {"type": "list", "options": {"none": "none", "slide": "slide", "pop": "pop", "fade": "fade", "flip": "flip"}, "value": "none", "hide": true}, + "pageTransitions": {"type": "list", "widgettype": "typeahead", "options": {"none": "none", "slide": "slide", "pop": "pop", "fade": "fade", "flip": "flip"}, "value": "none", "hide": true}, "dataSet": {"hide": true, "value": []} }, "wm.NotificationVariable": { @@ -148,13 +148,15 @@ wm.variables.factories.BaseVariablePropertyFactory = [ "operation": {"type": "list", "hide": true, "options": [], "required": true}, "autoUpdate": {"type": "boolean", "widgettype": "boolean-inputfirst", "value": false, "hide": true}, "startUpdate": {"type": "boolean", "widgettype": "boolean-inputfirst", "value": false, "hide": true}, + "spinnerContext": {"type": "list", "options": {"": "", "page": "page"}, "placeholder": "Search Widgets", "widgettype": "typeahead", "hide": true}, + "spinnerMessage": {"type": "string", "hide": true}, /*events*/ "onSuccess": {"type": "event", "options": variableEventOptions}, "onError": {"type": "event", "options": variableEventOptions}, "onProgress": {"type": "event", "options": variableEventOptions, "hide": true}, "onOnline": {"type": "event", "options": variableEventOptions, "hide": true}, "onOffline": {"type": "event", "options": variableEventOptions, "hide": true}, - "onBeforePush": {"type": "event", "options": variableEventOptions, "hide": true} + "onBefore": {"type": "event", "options": variableEventOptions, "hide": true} }, "wm.WebSocketVariable": { "name": {"type": "string", "required": true, "pattern": variableRegex}, diff --git a/src/main/webapp/scripts/modules/variables/application/base/baseservice.js b/src/main/webapp/scripts/modules/variables/application/base/baseservice.js index bedf5e850..c113a32ee 100644 --- a/src/main/webapp/scripts/modules/variables/application/base/baseservice.js +++ b/src/main/webapp/scripts/modules/variables/application/base/baseservice.js @@ -24,7 +24,8 @@ wm.variables.services.Variables = [ "BindingManager", "MetaDataFactory", "WIDGET_CONSTANTS", - function ($rootScope, BaseVariablePropertyFactory, ProjectService, FileService, VariableService, CONSTANTS, VARIABLE_CONSTANTS, DialogService, $timeout, Utils, BindingManager, MetaDataFactory, WIDGET_CONSTANTS) { + "$q", + function ($rootScope, BaseVariablePropertyFactory, ProjectService, FileService, VariableService, CONSTANTS, VARIABLE_CONSTANTS, DialogService, $timeout, Utils, BindingManager, MetaDataFactory, WIDGET_CONSTANTS, $q) { "use strict"; /** @@ -50,7 +51,7 @@ wm.variables.services.Variables = [ var runMode = CONSTANTS.isRunMode, DOT_EXPR_REX = /^\[("|')[\w\W]*(\1)\]$/g, MAIN_PAGE = 'Main', - startUpdateQueue = [], + startUpdateQueue = {}, lazySartUpdateQueue = {}, internalBoundNodeMap = {}, variableConfig = { @@ -99,7 +100,8 @@ wm.variables.services.Variables = [ "defaultName" : "loginVariable", "appOnly" : true, "spinnerInFlight": true, - "newVariableKey": "New LoginVariable" + "newVariableKey": "New LoginVariable", + "hideInEvents" : true }, "wm.LogoutVariable": { "collectionType" : "data", @@ -107,7 +109,8 @@ wm.variables.services.Variables = [ "defaultName" : "logoutVariable", "appOnly" : true, "spinnerInFlight": true, - "newVariableKey": "New LogoutVariable" + "newVariableKey": "New LogoutVariable", + "hideInEvents" : true }, "wm.TimerVariable": { "collectionType": "data", @@ -517,7 +520,11 @@ wm.variables.services.Variables = [ if ((newVal === oldVal && WM.isUndefined(newVal)) || (WM.isUndefined(newVal) && (!WM.isUndefined(oldVal) || !WM.isUndefined(targetObj[targetNodeKey])))) { return; } - setValueToNode(target, obj, root, variable, Utils.getClonedObject(newVal)); // clonning newVal to keep the source clean + //Skip cloning for blob column + if (!_.includes(['blob', 'file'], obj.type)) { + newVal = Utils.getClonedObject(newVal); + } + setValueToNode(target, obj, root, variable, newVal); // clonning newVal to keep the source clean if (runMode) { if (WM.isObject(newVal)) { @@ -641,13 +648,14 @@ wm.variables.services.Variables = [ * @param variable */ makeVariableCall = function (variable) { - var method; + var method, deferredVariableCall = $q.defer(); switch (variable.category) { case 'wm.ServiceVariable': method = 'update'; break; case 'wm.WebSocketVariable': method = 'open'; + deferredVariableCall.resolve(); break; case 'wm.LiveVariable': /* @@ -666,14 +674,18 @@ wm.variables.services.Variables = [ break; case 'wm.TimerVariable': method = 'fire'; + deferredVariableCall.resolve(); break; case 'wm.DeviceVariable': method = 'invoke'; break; } if (WM.isFunction(variable[method])) { - variable[method](); + variable[method](undefined, deferredVariableCall.resolve, deferredVariableCall.reject); + } else { + deferredVariableCall.reject(); } + return deferredVariableCall.promise; }, /** @@ -689,10 +701,124 @@ wm.variables.services.Variables = [ lazySartUpdateQueue[scope.$id] = lazySartUpdateQueue[scope.$id] || []; lazySartUpdateQueue[scope.$id].push(variable); } else { - startUpdateQueue.push(variable); + startUpdateQueue[scope.$id] = startUpdateQueue[scope.$id] || []; + startUpdateQueue[scope.$id].push(variable); } }, + /* + * Trigger update on variable based on run/studio mode + * */ + updateVariableData = function (variable, name, context, scope, options) { + /* assign variable name to the variable object for later use */ + variable.name = name; + if (variable.init) { + variable.init(); + } + if (runMode) { + variable.activeScope = scope; + } else { + /* this copy is used by binding dialog in STUDIO mode */ + self.studioCopy[context][name] = variable; + } + /* update variable bindings */ + updateVariableBinding(variable, name, scope); + + /*iterating over the collection to update the variables appropriately.*/ + if (variable.category === "wm.Variable") { + /* + * Case: a LIST type static variable having only one object + * and the object has all fields empty, remove that object + */ + if (CONSTANTS.isRunMode && variable.isList && variable.dataSet.length === 1) { + var firstObj = variable.dataSet[0], + isEmpty = true, + checkEmpty = function (obj) { + _.forEach(obj, function (value) { + if (!_.isEmpty(value)) { + if (_.isObject(value)) { + if (_.isArray(value)) { + //If array, check if array is empty or if it has only one value and the value is empty + isEmpty = _.isEmpty(value) || (value.length === 1 ? _.isEmpty(value[0]) : false); + } else { + //If object, loop over the object to check if it is empty or not + checkEmpty(value); + } + } else { + isEmpty = false; + } + } + return isEmpty; + }); + }; + checkEmpty(firstObj); + if (isEmpty) { + variable.dataSet = []; + } + } + } else if (variable.category === "wm.ServiceVariable") { + if (runMode) { + variable.canUpdate = true; + if (variable.startUpdate) { + processVariableStartUpdate(variable, scope); + } + } else { + //fetching the meta data in design mode always + if (WM.isFunction(variable.update)) { + variable.update(options); + } + } + } else if (variable.category === "wm.WebSocketVariable") { + if (runMode) { + if (variable.startUpdate) { + processVariableStartUpdate(variable, scope); + } + } else { + //fetching the meta data in design mode always + if (WM.isFunction(variable.update)) { + variable.update(); + } + } + } else if (variable.category === "wm.LiveVariable") { + migrateOrderBy(variable); + if (runMode) { + variable.canUpdate = true; + if (variable.startUpdate) { + processVariableStartUpdate(variable, scope); + } + } else { + if (variable.startUpdate && WM.isFunction(variable.update)) { + $timeout(function () { + variable.update(); + }, null, false); + } else { + /* + * In studio mode, DB and table related data is to be fetched and saved in the variable + * So, getData is called in STUDIO mode for liva variables with all types of operations + * since startUpdate is unset, table data is not required, hence skipFetchData flag is set + */ + $timeout(function () { + /* keeping the call in a timeout to wait for the widgets to load first and the binding to take effect */ + if (WM.isFunction(variable.update)) { + variable.update({skipFetchData: true}); + } + }, null, false); + } + } + } else if (variable.category === "wm.LoginVariable") { + if (runMode && variable.startUpdate) { + processVariableStartUpdate(variable, scope); + } + } else if (variable.category === "wm.TimerVariable") { + if (runMode && variable.autoStart) { + processVariableStartUpdate(variable, scope); + } + } else if (variable.category === "wm.DeviceVariable") { + if (runMode && variable.startUpdate) { + processVariableStartUpdate(variable, scope); + } + } + }, /* * Updates the variables in a context with their latest values * context refers to the namespace for the variables collection, like 'app'/page/partial/prefab @@ -724,100 +850,9 @@ wm.variables.services.Variables = [ } }); } - WM.forEach(self.variableCollection[scope.$id], function (variable, name) { - /* assign variable name to the variable object for later use */ - variable.name = name; - if (variable.init) { - variable.init(); - } - if (runMode) { - variable.activeScope = scope; - } else { - /* this copy is used by binding dialog in STUDIO mode */ - self.studioCopy[context][name] = variable; - } - - /* update variable bindings */ - updateVariableBinding(variable, name, scope); - - /*iterating over the collection to update the variables appropriately.*/ - if (variable.category === "wm.Variable") { - /* - * Case: a LIST type static variable having only one object - * and the object has all fields empty, remove that object - */ - if (CONSTANTS.isRunMode && variable.isList && variable.dataSet.length === 1) { - var obj = variable.dataSet[0], - keys = Object.keys(obj), - isValueEmpty = function (val) { - return _.isEmpty(obj[val]); - }; - if (keys.every(isValueEmpty)) { - variable.dataSet = []; - } - } - } else if (variable.category === "wm.ServiceVariable") { - if (runMode) { - variable.canUpdate = true; - if (variable.startUpdate) { - processVariableStartUpdate(variable, scope); - } - } else { - //fetching the meta data in design mode always - if (WM.isFunction(variable.update)) { - variable.update(); - } - } - } else if (variable.category === "wm.WebSocketVariable") { - if (runMode) { - if (variable.startUpdate) { - processVariableStartUpdate(variable, scope); - } - } else { - //fetching the meta data in design mode always - if (WM.isFunction(variable.update)) { - variable.update(); - } - } - } else if (variable.category === "wm.LiveVariable") { - migrateOrderBy(variable); - if (runMode) { - variable.canUpdate = true; - if (variable.startUpdate) { - processVariableStartUpdate(variable, scope); - } - } else { - if (variable.startUpdate && WM.isFunction(variable.update)) { - $timeout(function () { - variable.update(); - }, null, false); - } else { - /* - * In studio mode, DB and table related data is to be fetched and saved in the variable - * So, getData is called in STUDIO mode for liva variables with all types of operations - * since startUpdate is unset, table data is not required, hence skipFetchData flag is set - */ - $timeout(function () { - /* keeping the call in a timeout to wait for the widgets to load first and the binding to take effect */ - if (WM.isFunction(variable.update)) { - variable.update({skipFetchData: true}); - } - }, null, false); - } - } - } else if (variable.category === "wm.LoginVariable") { - if (runMode && variable.startUpdate) { - processVariableStartUpdate(variable, scope); - } - } else if (variable.category === "wm.TimerVariable") { - if (runMode && variable.autoStart) { - processVariableStartUpdate(variable, scope); - } - } else if (variable.category === "wm.DeviceVariable") { - if (runMode && variable.startUpdate) { - processVariableStartUpdate(variable, scope); - } - } + _.forEach(self.variableCollection[scope.$id], function (variable, name) { + //Trigger update on variable based on run/studio mode + updateVariableData(variable, name, context, scope); }); }, @@ -949,9 +984,14 @@ wm.variables.services.Variables = [ // removing dataSet for live variable if (!runMode && variable.category === "wm.LiveVariable") { variables[name].dataSet = []; - } else if (runMode && (variable.category === "wm.ServiceVariable" ||variable.category === "wm.WebSocketVariable")) { + } else if (runMode && (variable.category === "wm.ServiceVariable" || variable.category === "wm.WebSocketVariable")) { // Attaching service operation info to variables if in run mode variables[name]._wmServiceOperationInfo = MetaDataFactory.getByOperationId(variable.operationId, variable._prefabName); + + // service variable migration for old service variables not having controller names + if (runMode && !variable.controller && variable.operationId) { + variables[name].controller = variable.operationId.split('_')[0].replace(/Controller$/, ''); + } } }); @@ -1391,6 +1431,10 @@ wm.variables.services.Variables = [ varCollectionObj[scope.$id][name].name = name; self.studioCopy[owner][name] = varCollectionObj[scope.$id][name]; + if (variableObj.category === 'wm.LiveVariable') { + variableObj._isNew = true; + } + /* if app level variable make it available in the active page scope */ if (owner === VARIABLE_CONSTANTS.OWNER.APP) { if ($rootScope.activePageName && pageScopeMap[$rootScope.activePageName]) { @@ -1403,13 +1447,13 @@ wm.variables.services.Variables = [ } } if (!_.includes(CRUDMAP.CREATE[owner], name)) { - CRUDMAP.CREATE[owner].push(name);/*Storing created variable name in map*/ + CRUDMAP.CREATE[owner].push(name);/*Storing created variable name in map*/ } if (isUpdate) { - call('getData', name, {scope: scope, skipFetchData: !fetchData}); + updateVariableData(varCollectionObj[scope.$id][name], name, owner, scope, {skipFetchData: !fetchData}) } }, - initiateCallback = function (event, variable, response, info) { + initiateCallback = function (event, variable, response, info, skipDefaultNotification) { /*checking if event is available and variable has event property and variable event property bound to function*/ var eventValues = variable[event], retVal, @@ -1417,7 +1461,7 @@ wm.variables.services.Variables = [ callBackScope = variable.activeScope; if (eventValues) { retVal = Utils.triggerCustomEvents(event, eventValues, callBackScope, response, variable, info); - } else if (event === VARIABLE_CONSTANTS.EVENT.ERROR) { + } else if ((event === VARIABLE_CONSTANTS.EVENT.ERROR) && !skipDefaultNotification) { /* in case of error, if no event assigned, handle through default notification variable */ errorVariable = getVariableByName(VARIABLE_CONSTANTS.DEFAULT_VAR.NOTIFICATION); if (errorVariable) { @@ -1458,14 +1502,24 @@ wm.variables.services.Variables = [ delete self.variableCollection[scopeId]; }, + //Trigger error handler before discarding queued requests + triggerError = function (requestQueue) { + _.forEach(requestQueue, function (requestObj) { + Utils.triggerFn(requestObj && requestObj.error); + }); + }, + /* process the requests in the queue for a variable based on the inFlightBehavior flag of the variable */ - processRequestQueue = function (variable, requestQueue, handler) { + processRequestQueue = function (variable, requestQueue, handler, options) { /* process request queue for the variable only if it is not empty */ if (requestQueue && requestQueue[variable.name] && requestQueue[variable.name].length) { - var requestObj; - switch (variable.inFlightBehavior) { + var requestObj, + inFlightBehavior = _.get(options, 'inFlightBehavior') || variable.inFlightBehavior; + + switch (inFlightBehavior) { case 'executeLast': requestObj = requestQueue[variable.name].pop(); + triggerError(requestQueue); handler(requestObj.variable, requestObj.options, requestObj.success, requestObj.error); requestQueue[variable.name] = null; break; @@ -1474,6 +1528,7 @@ wm.variables.services.Variables = [ handler(requestObj.variable, requestObj.options, requestObj.success, requestObj.error); break; default: + triggerError(requestQueue); requestQueue[variable.name] = null; break; } @@ -1636,8 +1691,32 @@ wm.variables.services.Variables = [ getBindMap(field.type, curFieldObj, oldBindings[fieldName], visitedNodes); }); } - }; + }, + getEvaluatedOrderBy = function (varOrder, optionsOrder) { + var optionFields, + varOrderBy; + //If options order by is not defined, return variable order + if (!optionsOrder || WM.element.isEmptyObject(optionsOrder)) { + return varOrder; + } + //If variable order by is not defined, return options order + if (!varOrder) { + return optionsOrder; + } + //If both are present, combine the options order and variable order, with options order as precedence + varOrder = _.split(varOrder, ','); + optionsOrder = _.split(optionsOrder, ','); + optionFields = _.map(optionsOrder, function (order) { + return _.split(_.trim(order), ' ')[0]; + }); + //If a field is present in both options and variable, remove the variable orderby + _.remove(varOrder, function (orderBy) { + return _.includes(optionFields, _.split(_.trim(orderBy), ' ')[0]); + }); + varOrderBy = varOrder.length ? ',' + _.join(varOrder, ',') : ''; + return _.join(optionsOrder, ',') + varOrderBy; + }; /* * This object is used to collect all the variables and keep them organized * based on their nature. @@ -1670,14 +1749,31 @@ wm.variables.services.Variables = [ * This delay is to wait for the widgets to compile so that the same(and app variables) can be consumed as input to the variables. */ if (CONSTANTS.isRunMode) { - $rootScope.$on('page-ready', function () { - _.forEach(startUpdateQueue, makeVariableCall); - startUpdateQueue = []; + $rootScope.$on('page-ready', function (e, pageName) { + /* + * checking on page name's equality to active page name. + * when swift navigation among two pages is done, + * the event for first page is emitted after the second page and its variables are loaded + */ + var pageScope = pageScopeMap[pageName], + pageScopeId = pageScope.$id; + $q.all(_.map(startUpdateQueue[pageScopeId], makeVariableCall)) + .finally(function () { + $rootScope.$emit('page-startupdate-variables-loaded', pageName); + }); + delete startUpdateQueue[pageScopeId]; + + if (startUpdateQueue[$rootScope.$id]) { + _.forEach(startUpdateQueue[$rootScope.$id], makeVariableCall); + delete startUpdateQueue[$rootScope.$id]; + } }); $rootScope.$on('partial-ready', function (event, scope) { - if (lazySartUpdateQueue[scope.$id]) { - _.forEach(lazySartUpdateQueue[scope.$id], makeVariableCall); - lazySartUpdateQueue[scope.$id] = undefined; + var queue = lazySartUpdateQueue[scope.$id] || startUpdateQueue[scope.$id]; + if (queue) { + _.forEach(queue, makeVariableCall); + delete lazySartUpdateQueue[scope.$id]; + delete startUpdateQueue[scope.$id]; } }); } @@ -2142,6 +2238,7 @@ wm.variables.services.Variables = [ /*Set the "liveSource" and "type" properties of the live-variable.*/ createdVariable.liveSource = variableDetails.service; createdVariable.type = variableDetails.table; + createdVariable.package = variableDetails.package; createdVariable.category = variableCategory; createdVariable.isDefault = true; _.forEach(['maxResults', 'startUpdate', 'autoUpdate', 'bindCount'], function (property) { @@ -2381,7 +2478,17 @@ wm.variables.services.Variables = [ }, getVariableConfig: function () { return variableConfig; - } + }, + /** + * @ngdoc method + * @name $Variables#getEvaluatedOrderBy + * @methodOf wm.variables.$Variables + * @description + * combines variable orderby and options orderby + * @params {string} varOrder variable order by + * @params {string} optionsOrder options order by + */ + getEvaluatedOrderBy: getEvaluatedOrderBy }; return returnObject; diff --git a/src/main/webapp/scripts/modules/variables/application/livevariable/livevariableservice.js b/src/main/webapp/scripts/modules/variables/application/livevariable/livevariableservice.js index f48d48da8..0f1f556bc 100644 --- a/src/main/webapp/scripts/modules/variables/application/livevariable/livevariableservice.js +++ b/src/main/webapp/scripts/modules/variables/application/livevariable/livevariableservice.js @@ -168,6 +168,7 @@ wm.variables.services.$liveVariable = [ DataModelDesignManager.getDataModel(projectID, variable.liveSource, false, function (database) { var variableTable, variableType, + firstPrimaryKey, tableNameToEntityNameMap = {}, entityNameToTableNameMap = {}, getJavaType = function (javaType) { @@ -373,8 +374,16 @@ wm.variables.services.$liveVariable = [ } }); - setVariableProp(variable, writableVariable, "propertiesMap", tableDetails[variableType]); + setVariableProp(variable, writableVariable, 'propertiesMap', tableDetails[variableType]); + if (writableVariable && writableVariable._isNew) { + //For new variable, if orderby is not set, set the default orderby as primary field with ascending order + firstPrimaryKey = _.head(variable.propertiesMap.primaryFields); + if (!writableVariable.orderBy && firstPrimaryKey) { + setVariableProp(variable, writableVariable, 'orderBy', firstPrimaryKey + ' asc'); + } + variable._isNew = writableVariable._isNew = false; + } Utils.triggerFn(callback, projectID, variable, options, success); }, WM.noop); }, @@ -644,7 +653,7 @@ wm.variables.services.$liveVariable = [ } query = 'q=' + query; } - orderByFields = (!options.orderBy || WM.element.isEmptyObject(options.orderBy)) ? variable.orderBy : options.orderBy; + orderByFields = Variables.getEvaluatedOrderBy(variable.orderBy, options.orderBy); orderByOptions = orderByFields ? 'sort=' + orderByFields : ''; return { @@ -694,7 +703,7 @@ wm.variables.services.$liveVariable = [ /* process next requests in the queue */ variableActive[variable.activeScope.$id][variable.name] = false; - processRequestQueue(variable, requestQueue[variable.activeScope.$id], deployProjectAndFetchData); + processRequestQueue(variable, requestQueue[variable.activeScope.$id], deployProjectAndFetchData, options); }, null, false); } }; @@ -705,8 +714,9 @@ wm.variables.services.$liveVariable = [ output = initiateCallback(VARIABLE_CONSTANTS.EVENT.BEFORE_UPDATE, variable, clonedFields); if (output === false) { variableActive[variable.activeScope.$id][variable.name] = false; - processRequestQueue(variable, requestQueue[variable.activeScope.$id], deployProjectAndFetchData); + processRequestQueue(variable, requestQueue[variable.activeScope.$id], deployProjectAndFetchData, options); $rootScope.$emit('toggle-variable-state', variable.name, false); + Utils.triggerFn(error); return; } variable.canUpdate = false; @@ -787,7 +797,7 @@ wm.variables.services.$liveVariable = [ if (CONSTANTS.isRunMode) { /* process next requests in the queue */ variableActive[variable.activeScope.$id][variable.name] = false; - processRequestQueue(variable, requestQueue[variable.activeScope.$id], deployProjectAndFetchData); + processRequestQueue(variable, requestQueue[variable.activeScope.$id], deployProjectAndFetchData, options); } /* if callback function is provided, send the data to the callback */ Utils.triggerFn(success, dataObj.data, variable.propertiesMap, dataObj.pagingOptions); @@ -986,8 +996,9 @@ wm.variables.services.$liveVariable = [ output = initiateCallback(VARIABLE_CONSTANTS.EVENT.BEFORE_UPDATE, variableDetails, clonedFields); if (output === false) { variableActive[variableDetails.activeScope.$id][variableDetails.name] = false; - processRequestQueue(variableDetails, requestQueue[variableDetails.activeScope.$id], deployProjectAndFetchData); + processRequestQueue(variableDetails, requestQueue[variableDetails.activeScope.$id], deployProjectAndFetchData, options); $rootScope.$emit('toggle-variable-state', variableDetails.name, false); + Utils.triggerFn(error); return; } inputFields = _.isObject(output) ? output : clonedFields; diff --git a/src/main/webapp/scripts/modules/variables/application/loginvariable/loginvariableservice.js b/src/main/webapp/scripts/modules/variables/application/loginvariable/loginvariableservice.js index 0773991ea..9ce6e05ae 100644 --- a/src/main/webapp/scripts/modules/variables/application/loginvariable/loginvariableservice.js +++ b/src/main/webapp/scripts/modules/variables/application/loginvariable/loginvariableservice.js @@ -41,26 +41,12 @@ wm.variables.services.LoginVariableService = ['Variables', methods = { login: function (variable, options, success, error) { var params = {}, - variableOwner = variable.owner, variableEvents = VARIABLE_CONSTANTS.EVENTS, - callBackScope, errMsg, paramKey, output, loginInfo = {}; - /* get the callback scope for the variable based on its owner */ - if (variableOwner === "App") { - /* TODO: to look for a better option to get App/Page the controller's scope */ - callBackScope = $rootScope || {}; - } else { - if (variable._prefabName) { - callBackScope = options.scope || {}; - } else { - callBackScope = (options.scope && options.scope.$$childTail) ? options.scope.$$childTail : {}; - } - } - /* If login info provided along explicitly with options, don't look into the variable bindings for the same */ if (options.loginInfo) { loginInfo = options.loginInfo; @@ -81,19 +67,22 @@ wm.variables.services.LoginVariableService = ['Variables', /* if in RUN mode, trigger error events associated with the variable */ if (CONSTANTS.isRunMode) { Utils.triggerFn(error, errMsg); - initiateCallback("onError", variable, callBackScope, errMsg); + initiateCallback("onError", variable, errMsg); } return; } //Triggering 'onBeforeUpdate' and considering - output = initiateCallback(VARIABLE_CONSTANTS.EVENT.BEFORE_UPDATE, variable, callBackScope, params); + output = initiateCallback(VARIABLE_CONSTANTS.EVENT.BEFORE_UPDATE, variable, params); if (_.isObject(output)) { params = output; } else if (output === false) { + Utils.triggerFn(error); return; } + $rootScope.$emit('toggle-variable-state', variable.name, true); variable.promise = SecurityService.appLogin(params, function (response) { + $rootScope.$emit('toggle-variable-state', variable.name, false); var redirectUrl = response && response.url ? response.url : 'index.html', appManager = Utils.getService("AppManager"), lastLoggedinUser = SecurityService.getLastLoggedInUser(); @@ -114,7 +103,7 @@ wm.variables.services.LoginVariableService = ['Variables', WM.forEach(variableEvents, function (event) { if (event !== 'onError' && event !== VARIABLE_CONSTANTS.EVENT.BEFORE_UPDATE) { - initiateCallback(event, variable, callBackScope, _.get(config, 'userInfo')); + initiateCallback(event, variable, _.get(config, 'userInfo')); } }); @@ -155,10 +144,11 @@ wm.variables.services.LoginVariableService = ['Variables', $rootScope._noRedirect = undefined; }); }, function (errorMsg) { + $rootScope.$emit('toggle-variable-state', variable.name, false); errorMsg = errorMsg || "Invalid credentials."; /* if in RUN mode, trigger error events associated with the variable */ if (CONSTANTS.isRunMode) { - initiateCallback("onError", variable, callBackScope, errorMsg); + initiateCallback("onError", variable, errorMsg); } Utils.triggerFn(error, errorMsg); }); diff --git a/src/main/webapp/scripts/modules/variables/application/logoutvariable/logoutvariableservice.js b/src/main/webapp/scripts/modules/variables/application/logoutvariable/logoutvariableservice.js index 7bca32997..10b89ed70 100644 --- a/src/main/webapp/scripts/modules/variables/application/logoutvariable/logoutvariableservice.js +++ b/src/main/webapp/scripts/modules/variables/application/logoutvariable/logoutvariableservice.js @@ -41,72 +41,66 @@ wm.variables.services.LogoutVariableService = ['Variables', methods = { logout: function (variable, options, success, error) { - var variableOwner = variable.owner, - variableEvents = VARIABLE_CONSTANTS.EVENTS, - callBackScope, + var variableEvents = VARIABLE_CONSTANTS.EVENTS, logoutErrorMessage = "No authenticated user to logout.", handleError, redirectPage, - appManager; - - /* get the callback scope for the variable based on its owner */ - if (variableOwner === "App") { - /* TODO: to look for a better option to get App/Page the controller's scope */ - callBackScope = $rootScope || {}; - } else { - if (variable._prefabName) { - callBackScope = options.scope || {}; - } else { - callBackScope = (options.scope && options.scope.$$childTail) ? options.scope.$$childTail : {}; - } - } + appManager, + output; handleError = function (msg) { /* if in RUN mode, trigger error events associated with the variable */ if (CONSTANTS.isRunMode) { - initiateCallback("onError", variable, callBackScope, msg); + initiateCallback("onError", variable, msg); } Utils.triggerFn(error, msg); }; + $rootScope.$emit('toggle-variable-state', variable.name, true); + // EVENT: ON_BEFORE_UPDATE + output = initiateCallback(VARIABLE_CONSTANTS.EVENT.BEFORE_UPDATE, variable); + if (output === false) { + Utils.triggerFn(error); + return; + } SecurityService.isAuthenticated(function (isAuthenticated) { + $rootScope.$emit('toggle-variable-state', variable.name, false); if (isAuthenticated) { variable.promise = SecurityService.appLogout(function (redirectUrl) { - redirectUrl = Utils.getValidJSON(redirectUrl); + // Reset Security Config. + $rootScope.isUserAuthenticated = false; + appManager = Utils.getService("AppManager"); + appManager.resetSecurityConfig(). + then(function () { + // EVENT: ON_RESULT + initiateCallback(VARIABLE_CONSTANTS.EVENT.RESULT, variable, redirectUrl); + // EVENT: ON_SUCCESS + initiateCallback(VARIABLE_CONSTANTS.EVENT.SUCCESS, variable, redirectUrl); + }); + //In case of CAS response will be the redirectUrl + redirectUrl = Utils.getValidJSON(redirectUrl); if (redirectUrl) { $window.location.href = redirectUrl.result; - } else { - if (variable.useDefaultSuccessHandler) { - redirectPage = variable.redirectTo; - /* backward compatibility (index.html/login.html may be present in older projects) */ - if (!redirectPage || redirectPage === "login.html" || redirectPage === "index.html") { - redirectPage = ""; - } - $location.url(redirectPage); - $timeout(function () { - // reloading in timeout as, firefox and safari are not updating the url before reload(WMS-7887) - $window.location.reload(); - }); - } else if (CONSTANTS.isRunMode) { - appManager = Utils.getService("AppManager"); - appManager.resetSecurityConfig(). - then(function () { - WM.forEach(variableEvents, function (event) { - if (event !== "onError") { - initiateCallback(event, variable, callBackScope); - } - }); - }); + } else if (variable.useDefaultSuccessHandler) { + redirectPage = variable.redirectTo; + /* backward compatibility (index.html/login.html may be present in older projects) */ + if (!redirectPage || redirectPage === "login.html" || redirectPage === "index.html") { + redirectPage = ""; } + $location.url(redirectPage); + $timeout(function () { + // reloading in timeout as, firefox and safari are not updating the url before reload(WMS-7887) + $window.location.reload(); + }); } - $rootScope.isUserAuthenticated = false; Utils.triggerFn(success); }, handleError); } else { handleError(); } }, function () { + $rootScope.$emit('toggle-variable-state', variable.name, false); handleError(logoutErrorMessage); }); }, diff --git a/src/main/webapp/scripts/modules/variables/application/servicevariable/servicevariableservice.js b/src/main/webapp/scripts/modules/variables/application/servicevariable/servicevariableservice.js index f0df190ed..5b6a89c5d 100644 --- a/src/main/webapp/scripts/modules/variables/application/servicevariable/servicevariableservice.js +++ b/src/main/webapp/scripts/modules/variables/application/servicevariable/servicevariableservice.js @@ -154,23 +154,49 @@ wm.variables.services.$servicevariable = ['Variables', function isQueryServiceVar(variable) { return variable.controller === CONTROLLER_TYPE_QUERY && variable.serviceType === VARIABLE_CONSTANTS.SERVICE_TYPE_DATA; } - + /* + * Check for missing required params and format the date/time param values + * */ + function processRequestBody(inputData, params) { + var requestBody = {}, + missingParams = [], + paramValue; + _.forEach(params, function (param) { + paramValue = _.get(inputData, param.name); + if (WM.isDefined(paramValue) && (paramValue !== '')) { + paramValue = Utils.isDateTimeType(param.type) ? Utils.formatDate(paramValue, param.type) : paramValue; + //Construct ',' separated string if param is not array type but value is an array + if (WM.isArray(paramValue) && _.toLower(Utils.extractType(param.type)) === 'string') { + paramValue = _.join(paramValue, ','); + } + requestBody[param.name] = paramValue; + } else if (param.required) { + missingParams.push(param.name || param.id); + } + }); + return { + 'requestBody' : requestBody, + 'missingParams' : missingParams + }; + } /** * function to create the params to invoke the java service. creating the params and the corresponding * url to invoke based on the type of the parameter * @param operationInfo * @param variable + * @param inputFields to be considered for body type query/procedure variables * @returns {*} */ - function constructRestRequestParams(operationInfo, variable) { + function constructRestRequestParams(operationInfo, variable, inputFields) { variable = variable || {}; var queryParams = '', directPath = operationInfo.directPath || '', relativePath = operationInfo.basePath ? operationInfo.basePath + operationInfo.relativePath : operationInfo.relativePath, + bodyInfo, headers = {}, requestBody, url, - requiredParamMissing = false, + requiredParamMissing = [], target, pathParamRex, invokeParams, @@ -180,7 +206,10 @@ wm.variables.services.$servicevariable = ['Variables', method, formData, formDataContentType, - isProxyCall; + isProxyCall, + isBodyTypeQueryProcedure = ServiceFactory.isBodyTypeQueryProcedure(variable), + variableData, + params; function getFormDataObj() { if (formData) { @@ -204,11 +233,15 @@ wm.variables.services.$servicevariable = ['Variables', _.forEach(operationInfo.parameters, function (param) { var paramValue = param.sampleValue; - if (WM.isDefined(paramValue) && paramValue !== '') { + if ((WM.isDefined(paramValue) && paramValue !== '') || isBodyTypeQueryProcedure) { //Format dateTime params for dataService variables if (variable.serviceType === 'DataService' && Utils.isDateTimeType(param.type)) { paramValue = Utils.formatDate(paramValue, param.type); } + //Construct ',' separated string if param is not array type but value is an array + if (WM.isArray(paramValue) && _.toLower(Utils.extractType(param.type)) === 'string' && variable.serviceType === 'DataService') { + paramValue = _.join(paramValue, ','); + } switch (param.parameterType.toUpperCase()) { case 'QUERY': //Ignore null valued query params for queryService variable @@ -241,25 +274,42 @@ wm.variables.services.$servicevariable = ['Variables', headers[param.name] = paramValue; break; case 'BODY': - requestBody = paramValue; + //For post/put query methods wrap the input + if (isBodyTypeQueryProcedure) { + if (inputFields) { + variableData = inputFields; + params = _.get(operationInfo, ['definitions', param.type]); + } else { + //This is for Api Designer + variableData = paramValue || {}; + params = param.children; + } + bodyInfo = processRequestBody(variableData, params); + requestBody = bodyInfo.requestBody; + requiredParamMissing = _.concat(requiredParamMissing, bodyInfo.missingParams); + } else { + requestBody = paramValue; + } break; case 'FORMDATA': requestBody = Utils.getFormData(getFormDataObj(), param, paramValue); break; } } else if (param.required) { - requiredParamMissing = param.name || param.id; + requiredParamMissing.push(param.name || param.id); return false; } }); // if required param not found, return error + requiredParamMissing = requiredParamMissing.join(', '); if (requiredParamMissing) { return { 'error': { - 'type': 'required_field_missing', - 'field': requiredParamMissing, - 'message': 'Required field : "' + requiredParamMissing + '" missing' + 'type' : 'required_field_missing', + 'field' : requiredParamMissing, + 'message' : 'Required field(s) missing: "' + requiredParamMissing + '"', + 'skipDefaultNotification' : true } }; } @@ -314,7 +364,8 @@ wm.variables.services.$servicevariable = ['Variables', "headers": headers, "dataParams": requestBody, "authType": authType, - "isDirectCall": !isProxyCall + "isDirectCall": !isProxyCall, + "isExtURL": variable.serviceType === SERVICE_TYPE_REST }; return invokeParams; @@ -323,10 +374,10 @@ wm.variables.services.$servicevariable = ['Variables', /** * function to process error response from a service */ - function processErrorResponse(variable, errMsg, errorCB, xhrObj, skipNotification) { + function processErrorResponse(variable, errMsg, errorCB, xhrObj, skipNotification, skipDefaultNotification) { // EVENT: ON_ERROR if (!skipNotification) { - initiateCallback(VARIABLE_CONSTANTS.EVENT.ERROR, variable, errMsg, xhrObj); + initiateCallback(VARIABLE_CONSTANTS.EVENT.ERROR, variable, errMsg, xhrObj, skipDefaultNotification); } /* trigger error callback */ @@ -407,7 +458,7 @@ wm.variables.services.$servicevariable = ['Variables', } else if (param.name === "page") { param.sampleValue = options.page || param.sampleValue; } else if (param.name === "sort") { - param.sampleValue = options.orderBy || param.sampleValue || variable.orderBy; + param.sampleValue = Variables.getEvaluatedOrderBy(variable.orderBy, options.orderBy) || param.sampleValue; } } }); @@ -438,6 +489,7 @@ wm.variables.services.$servicevariable = ['Variables', if (CONSTANTS.isRunMode) { output = initiateCallback(VARIABLE_CONSTANTS.EVENT.BEFORE_UPDATE, variable, inputFields); if (output === false) { + Utils.triggerFn(error); return; } if (_.isObject(output)) { @@ -464,10 +516,10 @@ wm.variables.services.$servicevariable = ['Variables', } }; } else { - params = constructRestRequestParams(methodInfo, variable); + params = constructRestRequestParams(methodInfo, variable, inputFields); } if (params.error && params.error.message) { - processErrorResponse(variable, params.error.message, error, options.skipNotification); + processErrorResponse(variable, params.error.message, error, options.xhrObj, options.skipNotification, params.error.skipDefaultNotification); return; } } else if (serviceType === SERVICE_TYPE_REST) { @@ -846,24 +898,35 @@ wm.variables.services.$servicevariable = ['Variables', setInput: function (key, val, options) { return methods.setInput(this, key, val, options); }, - download: function (options) { - var inputParams = Utils.getClonedObject(this.dataBinding), - methodInfo = getMethodInfo(this, inputParams, {}); + download: function (options, errorHandler) { + var inputParams = Utils.getClonedObject(this.dataBinding), + methodInfo = getMethodInfo(this, inputParams, options), + requestParams; methodInfo.relativePath += '/export/' + options.exportFormat; - Utils.simulateFileDownload(constructRestRequestParams(methodInfo, this)); + requestParams = constructRestRequestParams(methodInfo, this); + + //If request params returns error then show an error toaster + if (_.hasIn(requestParams, 'error.message')) { + Utils.triggerFn(errorHandler, requestParams.error.message); + } else { + Utils.simulateFileDownload(requestParams); + } }, init: function () { if (this.isList) { Object.defineProperty(this, 'firstRecord', { 'get': function () { - return _.get(methods.getDataSet(this), 'content[0]', {}); + var dataSet = methods.getDataSet(this); + //For procedure(v1) data doesn't come under content + return _.head(dataSet && dataSet.content) || _.head(dataSet) || {}; } }); Object.defineProperty(this, 'lastRecord', { 'get': function () { - var content = _.get(methods.getDataSet(this), 'content', []); - return content[content.length - 1]; + var dataSet = methods.getDataSet(this); + //For procedure(v1) data doesn't come under content + return _.last(dataSet && dataSet.content) || _.last(dataSet) || {}; } }); } diff --git a/src/main/webapp/scripts/modules/variables/application/timervariable/timervariableservice.js b/src/main/webapp/scripts/modules/variables/application/timervariable/timervariableservice.js index 986446bea..33b331607 100644 --- a/src/main/webapp/scripts/modules/variables/application/timervariable/timervariableservice.js +++ b/src/main/webapp/scripts/modules/variables/application/timervariable/timervariableservice.js @@ -40,7 +40,7 @@ wm.variables.services.TimerVariableService = ['Variables', event = "onTimerFire", callBackScope = options.scope || $rootScope, exec = function () { - initiateCallback(event, variable, callBackScope); + initiateCallback(event, variable); }; variable._promise = repeatTimer ? $interval(exec, delay) : $timeout(function () { diff --git a/src/main/webapp/scripts/modules/variables/application/websocketvariable/websocketvariableservice.js b/src/main/webapp/scripts/modules/variables/application/websocketvariable/websocketvariableservice.js index a6839689d..d14f7971f 100644 --- a/src/main/webapp/scripts/modules/variables/application/websocketvariable/websocketvariableservice.js +++ b/src/main/webapp/scripts/modules/variables/application/websocketvariable/websocketvariableservice.js @@ -77,7 +77,7 @@ wm.variables.services.$websocketvariable = ['BaseVariablePropertyFactory', 'Vari function _onSocketOpen(variable, evt) { variable._socketConnected = true; // EVENT: ON_OPEN - initiateCallback(VARIABLE_CONSTANTS.EVENT.OPEN, variable, variable.activeScope, _.get(evt, 'data'), evt); + initiateCallback(VARIABLE_CONSTANTS.EVENT.OPEN, variable, _.get(evt, 'data'), evt); } /** @@ -91,7 +91,7 @@ wm.variables.services.$websocketvariable = ['BaseVariablePropertyFactory', 'Vari variable._socketConnected = false; freeSocket(variable); // EVENT: ON_CLOSE - initiateCallback(VARIABLE_CONSTANTS.EVENT.CLOSE, variable, variable.activeScope, _.get(evt, 'data'), evt); + initiateCallback(VARIABLE_CONSTANTS.EVENT.CLOSE, variable, _.get(evt, 'data'), evt); } /** @@ -105,7 +105,7 @@ wm.variables.services.$websocketvariable = ['BaseVariablePropertyFactory', 'Vari variable._socketConnected = false; freeSocket(variable); // EVENT: ON_ERROR - initiateCallback(VARIABLE_CONSTANTS.EVENT.ERROR, variable, variable.activeScope, _.get(evt, 'data') || 'Error while connecting with ' + variable.service, evt); + initiateCallback(VARIABLE_CONSTANTS.EVENT.ERROR, variable, _.get(evt, 'data') || 'Error while connecting with ' + variable.service, evt); } /** @@ -121,7 +121,7 @@ wm.variables.services.$websocketvariable = ['BaseVariablePropertyFactory', 'Vari var data = _.get(evt, 'data'), value, dataLength, dataLimit, shouldAddToLast, insertIdx; data = Utils.getValidJSON(data) || Utils.xmlToJson(data) || data; // EVENT: ON_MESSAGE - value = initiateCallback(VARIABLE_CONSTANTS.EVENT.MESSAGE_RECEIVE, variable, variable.activeScope, data, evt); + value = initiateCallback(VARIABLE_CONSTANTS.EVENT.MESSAGE_RECEIVE, variable, data, evt); data = WM.isDefined(value) ? value : data; if (shouldAppendData(variable)) { variable.dataSet = variable.dataSet || []; @@ -151,7 +151,7 @@ wm.variables.services.$websocketvariable = ['BaseVariablePropertyFactory', 'Vari */ function _onBeforeSend(variable, message) { // EVENT: ON_BEFORE_SEND - return initiateCallback(VARIABLE_CONSTANTS.EVENT.BEFORE_SEND, variable, variable.activeScope, message); + return initiateCallback(VARIABLE_CONSTANTS.EVENT.BEFORE_SEND, variable, message); } /** @@ -163,7 +163,7 @@ wm.variables.services.$websocketvariable = ['BaseVariablePropertyFactory', 'Vari */ function _onBeforeSocketClose(variable, evt) { // EVENT: ON_BEFORE_CLOSE - return initiateCallback(VARIABLE_CONSTANTS.EVENT.BEFORE_CLOSE, variable, variable.activeScope, _.get(evt, 'data'), evt); + return initiateCallback(VARIABLE_CONSTANTS.EVENT.BEFORE_CLOSE, variable, _.get(evt, 'data'), evt); } /** @@ -176,7 +176,7 @@ wm.variables.services.$websocketvariable = ['BaseVariablePropertyFactory', 'Vari */ function _onBeforeSocketOpen(variable, evt) { // EVENT: ON_BEFORE_OPEN - return initiateCallback(VARIABLE_CONSTANTS.EVENT.BEFORE_OPEN, variable, variable.activeScope, _.get(evt, 'data'), evt); + return initiateCallback(VARIABLE_CONSTANTS.EVENT.BEFORE_OPEN, variable, _.get(evt, 'data'), evt); } /** diff --git a/src/main/webapp/scripts/modules/variables/config.js b/src/main/webapp/scripts/modules/variables/config.js index 8e8803ed1..062776b25 100644 --- a/src/main/webapp/scripts/modules/variables/config.js +++ b/src/main/webapp/scripts/modules/variables/config.js @@ -21,7 +21,7 @@ wm.variables.filter(wm.variables.filters); /* Defining route path constants for wmCoreModule application */ wm.variables.constant('VARIABLE_CONSTANTS', { - EVENTS: ["onBeforeUpdate", "onResult", "onBeforeOpen", "onOpen", "onBeforeMessageSend", "onMessageReceive", "onError", "onBeforeDatasetReady", "onCanUpdate", "onClick", "onHide", "onOk", "onCancel", "onBeforeClose", "onClose", "onTimerFire", "onSuccess", "onProgress", "onOnline", "onOffline", "onBeforePush"], + EVENTS: ["onBeforeUpdate", "onResult", "onBeforeOpen", "onOpen", "onBeforeMessageSend", "onMessageReceive", "onError", "onBeforeDatasetReady", "onCanUpdate", "onClick", "onHide", "onOk", "onCancel", "onBeforeClose", "onClose", "onTimerFire", "onSuccess", "onProgress", "onOnline", "onOffline", "onBefore"], EVENT: { "CAN_UPDATE": "onCanUpdate", "BEFORE_UPDATE": "onBeforeUpdate", @@ -50,6 +50,7 @@ wm.variables.constant('VARIABLE_CONSTANTS', { SERVICE_TYPE_SECURITY: "SecurityServiceType", SERVICE_NAME_FEED: "FeedService", SERVICE_TYPE_WEBSOCKET: "WebSocketService", + BODY_FIELD: "bodyField", REST_SUPPORTED_SERVICES: ["JavaService", "SoapService", "FeedService", "RestService", "SecurityServiceType", "DataService", "WebSocketService"], PAGINATION_PARAMS: ["page", "size", "sort"], DEFAULT_VAR: { diff --git a/src/main/webapp/scripts/modules/widgets/advanced/calendar/calendar.js b/src/main/webapp/scripts/modules/widgets/advanced/calendar/calendar.js index 42ba4690f..f8c8f4601 100644 --- a/src/main/webapp/scripts/modules/widgets/advanced/calendar/calendar.js +++ b/src/main/webapp/scripts/modules/widgets/advanced/calendar/calendar.js @@ -34,12 +34,19 @@ WM.module('wm.widgets.advanced') }, VIEW_TYPES = { 'BASIC' : 'basic', - 'AGENDA': 'agenda' + 'AGENDA': 'agenda', + 'LIST' : 'list' }, SELECTION_MODES = { 'NONE' : 'none', 'SINGLE' : 'single', 'MULTIPLE' : 'multiple' + }, + LIST_BUTTONTEXT = { + 'DAY' : 'Day', + 'MONTH' : 'Month', + 'YEAR' : 'Year', + 'WEEK' : 'Week' }; /* datavalue property is removed from the calendar widget.*/ @@ -58,16 +65,20 @@ WM.module('wm.widgets.advanced') left += ' today'; } + if (_.includes(ctrls, 'year')) { + right += (viewType === VIEW_TYPES.LIST) ? 'listYear' : ''; + } + if (_.includes(ctrls, 'month')) { - right += ' month'; + right += (viewType === VIEW_TYPES.LIST) ? ' listMonth' : ' month'; } if (_.includes(ctrls, 'week')) { - right += viewType === VIEW_TYPES.BASIC ? ' basicWeek' : ' agendaWeek'; + right += (viewType === VIEW_TYPES.BASIC) ? ' basicWeek' : (viewType === VIEW_TYPES.LIST) ? ' listWeek' : ' agendaWeek'; } if (regEx.test(ctrls)) { - right += viewType === VIEW_TYPES.BASIC ? ' basicDay' : ' agendaDay'; + right += (viewType === VIEW_TYPES.BASIC) ? ' basicDay' : (viewType === VIEW_TYPES.LIST) ? ' listDay' : ' agendaDay'; } WM.extend($is.calendarOptions.calendar.header, {'left': left, 'right': right}); @@ -77,7 +88,7 @@ WM.module('wm.widgets.advanced') function calculateHeight(calendar, $el, $is) { var $parentEl = $el.parent(), parentHeight = $parentEl.css('height'), - elHeight = $is.height, + elHeight = $is.height || '100%', computedHeight; if (_.includes(elHeight, '%')) { if (_.includes(parentHeight, '%')) { @@ -150,7 +161,10 @@ WM.module('wm.widgets.advanced') eventSet.push(event); } }); - $is.eventSources.push(eventSet); + //as the calendar is not yet rendered, the fullcalendar tries to draw the events when events are bound. hence delay the event assignment. + $timeout(function () { + $is.eventSources.push(eventSet); + }); } } break; @@ -168,7 +182,7 @@ WM.module('wm.widgets.advanced') return; } if (!isMobile) { - if (newVal !== 'month') { + if (newVal !== 'month' || $is.calendartype === VIEW_TYPES.LIST) { calendar.defaultView = $is.calendartype + _.capitalize(newVal); } else { calendar.defaultView = newVal; @@ -301,7 +315,7 @@ WM.module('wm.widgets.advanced') $is.onEventclick({$event: jsEvent, $data: event, $view: view}); } function viewRenderProxy(view) { - $is.currentview = {start: view.start.format(), end: view.end.format()}; + $is.currentview = {start: view.start.format(), end: view.end.subtract(1, 'days').format()}; $is.onViewrender({$view: view}); } function eventRenderProxy(event, jsEvent, view) { @@ -309,20 +323,23 @@ WM.module('wm.widgets.advanced') $is.onEventrender({$event: {}, $data: event, $view: view}); } function setSelectedData(start, end) { - var filteredDates = [], - dataset = $is.dataset; + var filteredDates = [], + dataset = $is.dataset, + eventStartKey = $is.eventstart || 'start', + eventEndKey = $is.eventend || 'end', + startDate = moment(new Date(start)).format('MM/DD/YYYY'), + endDate = moment(new Date(end)).subtract(1, 'days').format('MM/DD/YYYY'); if (!dataset) { return; } dataset = dataset.data || dataset; _.forEach(dataset, function (value) { - if (!value.start) { + if (!value[eventStartKey]) { return; } - var eventDate = moment(new Date(value.start)).format('MM/DD/YYYY'), - startDate = moment(new Date(start)).format('MM/DD/YYYY'), - endDate = moment(new Date(end)).format('MM/DD/YYYY'), - eventExists = moment(eventDate).isSameOrAfter(startDate) && moment(eventDate).isBefore(endDate); + var eventStartDate = moment(new Date(value[eventStartKey])).format('MM/DD/YYYY'), + eventEndDate = moment(new Date(value[eventEndKey] || value[eventStartKey])).format('MM/DD/YYYY'), + eventExists = moment(startDate).isSame(eventStartDate) && moment(eventEndDate).isSame(endDate); if (eventExists) { filteredDates.push(value); } @@ -424,7 +441,6 @@ WM.module('wm.widgets.advanced') } else { $is.calendarOptions = { calendar: { - 'height' : parseInt($is.height, 10), 'editable' : true, 'selectable' : false, 'header' : headerOptions, @@ -436,11 +452,24 @@ WM.module('wm.widgets.advanced') 'select' : onSelectProxy, 'eventRender' : eventRenderProxy, 'viewRender' : viewRenderProxy, + 'unselectAuto' : false, 'dayNames' : $locale.DATETIME_FORMATS.DAY, 'dayNamesShort' : $locale.DATETIME_FORMATS.SHORTDAY, 'views' : { 'month': { 'eventLimit': 0 + }, + 'listDay': { + 'buttonText': LIST_BUTTONTEXT.DAY + }, + 'listWeek': { + 'buttonText': LIST_BUTTONTEXT.WEEK + }, + 'listMonth': { + 'buttonText': LIST_BUTTONTEXT.MONTH + }, + 'listYear': { + 'buttonText': LIST_BUTTONTEXT.YEAR } } } diff --git a/src/main/webapp/scripts/modules/widgets/advanced/login/login.js b/src/main/webapp/scripts/modules/widgets/advanced/login/login.js index fe1152ce7..65b60ea29 100644 --- a/src/main/webapp/scripts/modules/widgets/advanced/login/login.js +++ b/src/main/webapp/scripts/modules/widgets/advanced/login/login.js @@ -92,7 +92,7 @@ WM.module('wm.widgets.advanced') } if (rememberMeElement.length) { - $rememberMe = rememberMeElement.val(); + $rememberMe = rememberMeElement.scope().datavalue; } // prevent the actions when the userName/Pwd fields are not valid. diff --git a/src/main/webapp/scripts/modules/widgets/base/Base.js b/src/main/webapp/scripts/modules/widgets/base/Base.js index 4ee811a2f..81e36bd21 100644 --- a/src/main/webapp/scripts/modules/widgets/base/Base.js +++ b/src/main/webapp/scripts/modules/widgets/base/Base.js @@ -52,7 +52,7 @@ WM.module('wm.widgets.base', []) { "name" : "Decimal Digits", "groupOptions" : { - ".f" : "9", + ".0f" : "9", ".1f" : "9.9", ".2f" : "9.99", ".3f" : "9.999" @@ -152,49 +152,17 @@ WM.module('wm.widgets.base', []) 'value': 'top', 'label': 'Top' }, - { - 'value': 'top-left', - 'label': 'Top Left' - }, - { - 'value': 'top-right', - 'label': 'Top Right' - }, { 'value': 'bottom', 'label': 'Bottom' }, - { - 'value': 'bottom-left', - 'label': 'Bottom Left' - }, - { - 'value': 'bottom-right', - 'label': 'Bottom Right' - }, { 'value': 'left', 'label': 'Left' }, - { - 'value': 'left-top', - 'label': 'Left Top' - }, - { - 'value': 'left-bottom', - 'label': 'Left Bottom' - }, { 'value': 'right', 'label': 'Right' - }, - { - 'value': 'right-top', - 'label': 'Right Top' - }, - { - 'value': 'right-bottom', - 'label': 'Right Bottom' } ], EVERYONE = "Everyone", @@ -203,7 +171,7 @@ WM.module('wm.widgets.base', []) "wm.base": { "name": {"type": "string", "pattern": nameRegex, "maxlength": 32}, "show": {"type": "boolean", "value": true, "bindable": "in-bound"}, - "deferload": {"type": "boolean", "value": false, "show": false, "ignoreGetterSetters": true}, + "deferload": {"type": "boolean", "value": false, "show": false}, "class": {"type": "string", "pattern": classRegex}, "accessroles": {"type": "access-roles-select", "options": roles, "value": EVERYONE}, "showindevice": {"type": "select-all", "options": showInDeviceOptions, "value": "all", "displaytype": 'block'} @@ -329,6 +297,8 @@ WM.module('wm.widgets.base', []) }, "wm.icon": { "hint": {"type": "string", "bindable": "in-bound"}, + "caption": {"type": "string", "bindable": "in-out-bound", "maxlength": 256, "showPrettyExprInDesigner": true}, + "iconposition": {"type": "list", "options": ["left", "right"], "value": "left"}, "iconclass": {"type": "string", "value": "wi wi-star-border", "widget": "select-icon", "bindable": "in-out-bound", "pattern": classRegex}, "iconsize": {"type": "string", "pattern": dimensionRegex}, "animation": {"type": "list", "options": animationOptions}, @@ -358,7 +328,8 @@ WM.module('wm.widgets.base', []) "animation": {"type": "list", "options": animationOptions}, "shortcutkey": {"type": "string"}, "class": {"type": "string", "pattern": classRegex, "widget": "list-picker", "value": "btn-default", "options": ["btn-default", "btn-primary", "btn-info", "btn-warning", "btn-success", "btn-danger", "btn-inverse", "btn-lg", "btn-sm", "btn-xs", "btn-raised", "btn-fab", "btn-link", "btn-transparent", "no-border", "jumbotron"]}, - "margin": {"type": "string", "widget": "box-model"} + "margin": {"type": "string", "widget": "box-model"}, + "horizontalalign": {"type": "string", "options": ["left", "center", "right"], "widget": "icons-align", "show": false} }, "wm.rating": { "hint": {"show": false}, @@ -422,7 +393,7 @@ WM.module('wm.widgets.base', []) "height": {"type": "string", "pattern": dimensionRegex}, "datavalue": {"type": "string, object", "bindable": "in-out-bound", "widget": "string", "getTypeFrom": "dataset"}, "scopedatavalue": {"type": "string"}, - "dataset": {"type": "array, string", "bindable": "in-bound", "widget": "string", "value": "yes, no, maybe"}, + "dataset": {"type": "array, string", "bindable": "in-bound", "widget": "string", "value": "yes, no, maybe", "showPrettyExprInDesigner": true, "defaultvalue": "yes, no, maybe"}, "scopedataset": {"type": "string"}, "datafield": {"type": "list", "options": ["All Fields"], "value": "All Fields", "datasetfilter" : "terminals", "allfields" : true}, "displayfield": {"type": "list", "options": [""], "value": "", "datasetfilter": "terminals"}, @@ -457,7 +428,9 @@ WM.module('wm.widgets.base', []) "itemlabel": {"type": "string", "widget": "list", "options": [""], "bindable": "in-bound", "bindonly": "expression", "datasetfilter" : "terminals"}, "itemlink": {"type": "string", "widget": "list", "options": [""], "bindable": "in-bound", "bindonly": "expression", "datasetfilter" : "terminals"}, "itemicon": {"type": "string", "widget": "list", "options": [""], "bindable": "in-bound", "bindonly": "expression", "datasetfilter" : "terminals"}, - "itemchildren": {"type": "string", "widget": "list", "options": [""], "bindable": "in-bound", "bindonly": "expression", "datasetfilter" : "objects"} + "itemchildren": {"type": "string", "widget": "list", "options": [""], "bindable": "in-bound", "bindonly": "expression", "datasetfilter" : "objects"}, + "itemaction": {"type": "string", "widget": "list", "options": [""], "bindable": "in-bound", "bindonly": "expression", "datasetfilter" : "terminals"}, + "userrole": {"type": "string", "widget": "list", "options": [""], "bindable": "in-bound", "bindonly": "expression", "datasetfilter" : "terminals"} }, "wm.tree": { @@ -472,7 +445,7 @@ WM.module('wm.widgets.base', []) "nodeid": {"type": "string", "widget": "list", "datasetfilter" : "terminals", "bindable": "in-bound", "bindonly": "expression"}, "tabindex": {"type": "number", "value": "0"}, "levels": {"type": "number", "value": 0, "min": "0", "max": "10", "step": "1"}, - "datavalue": {"type": "string", "bindable": "in-out-bound", "widget": "tree-datavalue"}, + "datavalue": {"type": "string, number, boolean, date, time, object", "bindable": "in-out-bound", "widget": "tree-datavalue", "getTypeFrom": "expr:getDataValueType()"}, "orderby": {"type": "list", "widget": "order-by", "datasetfilter": "terminals"} }, @@ -541,8 +514,8 @@ WM.module('wm.widgets.base', []) "showweeks": {"type": "boolean", "value": false, "bindable": "in-bound"}, "readonly": {"type": "boolean", "bindable": "in-bound"}, "disabled": {"type": "boolean", "bindable": "in-bound"}, - "mindate": {"type": dateTimeTypes, "widget": "string", "bindable": "in-bound", "hint": "yyyy-MM-dd"}, - "maxdate": {"type": dateTimeTypes, "widget": "string", "bindable": "in-bound", "hint": "yyyy-MM-dd"}, + "mindate": {"type": dateTimeTypes, "widget": "data-list", "options": ["CURRENT_DATE"], "bindable": "in-bound", "hint": "yyyy-MM-dd"}, + "maxdate": {"type": dateTimeTypes, "widget": "data-list", "options": ["CURRENT_DATE"], "bindable": "in-bound", "hint": "yyyy-MM-dd"}, "datepattern": {"value": "yyyy-MM-dd", "type": "list", "options": [], "widget": "date-patterns"}, "outputformat": {"value": "yyyy-MM-dd", "type": "list", "options": [], "widget": "date-patterns"}, "datavalue": {"type": dateTimeTypes, "widget": "data-list", "options": ["CURRENT_DATE"], "bindable": "in-out-bound", "hint": "yyyy-MM-dd"}, @@ -562,7 +535,7 @@ WM.module('wm.widgets.base', []) }, "wm.calendar": { "backgroundcolor": {"type": "string", "widget": "color"}, - "width": {"type": "string", "pattern": dimensionRegex}, + "width": {"type": "string", "value": "100%", "pattern": dimensionRegex}, "height": {"type": "string", "pattern": dimensionRegex}, "dataset": {"type": "array, object", "bindable": "in-bound", "widget": "string"}, "scopedataset": {"type": "string"}, @@ -570,14 +543,14 @@ WM.module('wm.widgets.base', []) "selecteddates": {"type": "object", "widget": "string", "bindable": "in-out-bound", "getTypeFrom": "expr:getPropertyType('selecteddates')"}, "currentview": {"type": "object", "widget": "string", "bindable": "in-out-bound", "getTypeFrom": "expr:getPropertyType('currentview')"}, "selecteddata": {"type": "array, object", "isList": true, "show": false, "bindable": "out-bound", "getTypeFrom": "expr.getPropertyType('selecteddata')" }, - "calendartype": {"type": "list", "options": ["basic", "agenda"], "value": "basic"}, + "calendartype": {"type": "list", "options": ["basic", "agenda", "list"], "value": "basic"}, "eventstart": {"type": "list", "value": "start", "options": [""], "datasetfilter" : "terminals"}, "eventend": {"type": "list", "value": "end", "options": [""], "datasetfilter" : "terminals"}, "eventallday": {"type": "list", "value": "allday", "options": [""], "datasetfilter" : "terminals"}, "eventtitle": {"type": "list", "value": "title", "options": [""], "datasetfilter" : "terminals"}, "eventclass": {"type": "list", "value": "class", "options": [""], "datasetfilter" : "terminals"}, "view": {"type": "list", "options": ["month", "week", "day"], "value": "month"}, - "controls": {"type": "list", "options": "navigation, today, month, week, day", "value": "navigation, today, month, week, day", "widget": "select-all"}, + "controls": {"type": "list", "options": "navigation, today, year, month, week, day", "value": "navigation, today, year, month, week, day", "widget": "select-all"}, "onViewrender": {"type": "event", "options": widgetEventOptions, "widget": "eventlist"}, "onSelect": {"type": "event", "options": widgetEventOptions, "widget": "eventlist"}, "onEventdrop": {"type": "event", "options": widgetEventOptions, "widget": "eventlist"}, @@ -591,6 +564,7 @@ WM.module('wm.widgets.base', []) "controls": {"show": false}, "multiselect": {"show": false}, "calendartype": {"show": false}, + "selectionmode": {"show": false}, "onEventdrop": {"show": false}, "onEventresize": {"show": false}, "eventstart": {"type": "list", "value": "start", "options": [""], "datasetfilter" : "terminals"}, @@ -629,8 +603,8 @@ WM.module('wm.widgets.base', []) "disabled": {"type": "boolean", "bindable": "in-bound"}, "hourstep": {"type": "number", "value": 1}, "minutestep": {"type": "number", "value": 15}, - "mindate": {"type": dateTimeTypes, "widget": "string", "bindable": "in-bound", "hint": "yyyy-MM-dd"}, - "maxdate": {"type": dateTimeTypes, "widget": "string", "bindable": "in-bound", "hint": "yyyy-MM-dd"}, + "mindate": {"type": dateTimeTypes, "widget": "data-list", "options": ["CURRENT_DATE"], "bindable": "in-bound", "hint": "yyyy-MM-dd"}, + "maxdate": {"type": dateTimeTypes, "widget": "data-list", "options": ["CURRENT_DATE"], "bindable": "in-bound", "hint": "yyyy-MM-dd"}, "datepattern": {"value": "yyyy-MM-dd hh:mm:ss a", "type": "list", "options": [], "widget": "date-time-patterns"}, "outputformat": {"value": "timestamp", "type": "list", "options": [], "widget": "date-time-patterns"}, "datavalue": {"type": dateTimeTypes, "widget": "data-list", "options": ["CURRENT_DATE"], "bindable": "in-out-bound", "hint": "yyyy-MM-dd HH:mm:ss"}, @@ -702,7 +676,7 @@ WM.module('wm.widgets.base', []) "datavalue": {"type": "string", "bindable": "in-out-bound", "show": false, "getTypeFrom": "dataset"}, "scopedatavalue": {"type": "string"}, - "dataset": {"type": "array, object", "bindable": "in-bound", "widget": "string", "value": "Option 1, Option 2, Option 3"}, + "dataset": {"type": "array, object", "bindable": "in-bound", "widget": "string", "value": "Option 1, Option 2, Option 3", "showPrettyExprInDesigner": true, "defaultvalue": "Option 1, Option 2, Option 3"}, "usekeys": {"type": "boolean"}, "required": {"type": "boolean", "bindable": "in-bound", "value": false}, "selectedvalue": {"type": "string, number, boolean, date, time, object", "widget": "string", "bindable": "in-bound", "getTypeFrom": "dataset"}, @@ -711,6 +685,7 @@ WM.module('wm.widgets.base', []) "wm.colorpicker": { "readonly": {"type": "boolean", "value": false, "bindable": "in-bound"}, "disabled": {"type": "boolean", "value": false, "bindable": "in-bound"}, + "required": {"type": "boolean", "value": false, "bindable": "in-bound"}, "datavalue": {"type": "string", "bindable": "in-out-bound"}, "scopedatavalue": {"type": "string"}, "placeholder": {"type": "string", "value": "Select Color", "bindable": "in-bound"}, @@ -780,7 +755,7 @@ WM.module('wm.widgets.base', []) "datavalue": {"type": "string, array", "bindable": "in-out-bound", "show": false, "widget": "string", "getTypeFrom": "dataset"}, "scopedatavalue": {"type": "string"}, - "dataset": {"type": "array, object", "bindable": "in-bound", "widget": "string", "value": "Option 1, Option 2, Option 3"}, + "dataset": {"type": "array, object", "bindable": "in-bound", "widget": "string", "value": "Option 1, Option 2, Option 3", "showPrettyExprInDesigner": true, "defaultvalue": "Option 1, Option 2, Option 3"}, "usekeys": {"type": "boolean"}, "selectedvalues": {"type": "string, object", "isList": true, "bindable": "in-bound", "widget": "string", "getTypeFrom": "dataset"}, "onReady": {"type": "event", "options": widgetEventOptions, "widget": "eventlist"}, @@ -821,7 +796,7 @@ WM.module('wm.widgets.base', []) "shortcutkey": {"type": "string"}, "class": {"type": "string", "pattern": classRegex, "widget": "list-picker", "options": ["input-lg", "input-sm"]}, "backgroundcolor": {"type": "string", "widget": "color"}, - "displayValue": {"type": "string, array", "getIsListFrom": "expr:multiple", "show": false, "bindable": "out-bound"} + "displayValue": {"type": "string", "getIsListFrom": "expr:multiple", "show": false, "bindable": "out-bound"} }, "wm.marquee": { @@ -840,7 +815,8 @@ WM.module('wm.widgets.base', []) "animation": {"type": "list", "options": animationOptions}, "class": {"type": "string", "pattern": classRegex, "widget": "list-picker", "options": [ "h1", "h2", "h3", "h4", "h5", "h6", "p", "text-ellipsis", "text-left", "text-right", "text-center", "text-muted", "text-primary", "text-success", "text-info", "text-warning", "text-danger", "label-default", "label-primary", "label-success", "label-info", "label-warning", "label-danger", "vertical-align-top", "vertical-align-middle", "vertical-align-bottom", "lead", "badge", "form-control-static", "control-label"]}, "whitespace": {"type": "list", "options": [" ", "normal", "nowrap", "pre", "pre-line", "pre-wrap"], "value": " "}, - "wordbreak": {"type": "list", "options": ["break-word", "normal"]} + "wordbreak": {"type": "list", "options": ["break-word", "normal"]}, + "horizontalalign": {"type": "string", "options": ["left", "center", "right"], "widget": "icons-align", "show": false} }, "wm.picture": { @@ -879,7 +855,7 @@ WM.module('wm.widgets.base', []) "datavalue": {"type": "string", "bindable": "in-out-bound"}, "scopedatavalue": {"type": "string"}, "placeholder": {"type": "string", "value": "Place your text", "bindable": "in-bound"}, - "maxchars": {"type": "number"}, + "maxchars": {"type": "number", "bindable": "in-bound"}, "updateon": {"type": "list", "value": "blur", "widget": "update-on"}, "updatedelay": {"type": "number", "value": 0}, "shortcutkey": {"type": "string"}, @@ -901,7 +877,7 @@ WM.module('wm.widgets.base', []) "iconmargin": {"type": "string", "pattern": dimensionRegex}, "actiontitle": {"type": "string", "show": false, "pattern": dimensionRegex}, "actionlink": {"type": "string", "show": false, "pattern": dimensionRegex}, - "closable": {"type": "boolean", "show": false}, + "closable": {"type": "boolean", "show": true, "value": true}, "contentclass": {"type": "string", "pattern": classRegex, "show": false} }, "wm.dialog.dialogheader": { @@ -910,7 +886,7 @@ WM.module('wm.widgets.base', []) "iconwidth": {"type": "string", "pattern": dimensionRegex}, "iconheight": {"type": "string", "pattern": dimensionRegex}, "iconmargin": {"type": "string", "pattern": dimensionRegex}, - "closable": {"type": "boolean", "show": false} + "closable": {"type": "boolean", "show": true, "value": true} }, "wm.dialog.onOk": { "onOk": {"type": "event", "options": widgetEventOptions, "widget": "eventlist"} @@ -923,7 +899,7 @@ WM.module('wm.widgets.base', []) "message": {"type": "string", "value": "I am an alert box!", "bindable": "in-bound", "showPrettyExprInDesigner": true}, "alerttype": {"type": "list", "options": ["error", "information", "success", "warning"], "value": "error"}, "modal": {"type": "boolean", "value": false}, - "keyboard": {"type": "boolean", "value": true} + "keyboard": {"type": "boolean", "value": true, "show": false} }, "wm.confirmdialog": { "title": {"type": "string", "value": "Confirm", "bindable": "in-bound", "showPrettyExprInDesigner": true}, @@ -933,7 +909,7 @@ WM.module('wm.widgets.base', []) "message": {"type": "string", "value": "I am confirm box!", "bindable": "in-bound", "showPrettyExprInDesigner": true}, "onCancel": {"type": "event", "options": widgetEventOptions, "widget": "eventlist"}, "modal": {"type": "boolean", "value": false}, - "keyboard": {"type": "boolean", "value": true} + "keyboard": {"type": "boolean", "value": true, "show": false} }, "wm.iframedialog": { "title": {"type": "string", "value": "External Content", "bindable": "in-bound", "showPrettyExprInDesigner": true}, @@ -947,7 +923,7 @@ WM.module('wm.widgets.base', []) "showactions": {"type": "boolean", "value": true}, "showheader": {"type": "boolean", "value": true}, "modal": {"type": "boolean", "value": false}, - "keyboard": {"type": "boolean", "value": true} + "keyboard": {"type": "boolean", "value": true, "show": false} }, "wm.pagedialog": { "title": {"type": "string", "value": "Page Content", "bindable": "in-bound", "showPrettyExprInDesigner": true}, @@ -957,15 +933,15 @@ WM.module('wm.widgets.base', []) "closable": {"type": "boolean", "value": true}, "showactions": {"type": "boolean", "value": true}, "modal": {"type": "boolean", "value": false}, - "keyboard": {"type": "boolean", "value": true} + "keyboard": {"type": "boolean", "value": true, "show": false} }, "wm.logindialog": { "tabindex": {"show": false}, "height": {"type": "string", "show": false, "pattern": dimensionRegex}, "onClose": {"type": "event", "options": widgetEventOptions, "widget": "eventlist"}, - "closable": {"type": "boolean", "value": true}, + "closable": {"type": "boolean", "value": false, "show": false}, "modal": {"type": "boolean", "value": true}, - "keyboard": {"type": "boolean", "value": true}, + "keyboard": {"type": "boolean", "value": true, "show": false}, "onSubmit": {"type": "event", "options": widgetEventOptions, "widget": "eventlist"}, "title": {"type": "string", "maxlength": 256, "bindable": "in-bound", "showPrettyExprInDesigner": true}, "iconclass": {"type": "string", "widget": "select-icon", "bindable": "in-out-bound", "pattern": classRegex}, @@ -976,7 +952,7 @@ WM.module('wm.widgets.base', []) "wm.designdialog": { "modal": {"type": "boolean", "value": false}, "onClose": {"type": "event", "options": widgetEventOptions, "widget": "eventlist"}, - "keyboard": {"type": "boolean", "value": true}, + "keyboard": {"type": "boolean", "value": true, "show": false}, "closable": {"type": "boolean", "value": true}, "title": {"type": "string"}, "showheader": {"type": "boolean", "value": true} @@ -1031,7 +1007,7 @@ WM.module('wm.widgets.base', []) "name": {"type": "string", "pattern": nameRegex, "maxlength": 32}, "class": {"type": "string", "pattern": classRegex}, "show": {"type": "boolean", "value": true, "bindable": "in-bound"}, - "deferload": {"type": "boolean", "value": false, "show": false, "ignoreGetterSetters": true}, + "deferload": {"type": "boolean", "value": false, "show": false}, "height": {"type": "string", "pattern": dimensionRegex}, "width": {"type": "string", "pattern": dimensionRegex}, "accessroles": {"type": "access-roles-select", "options": roles, "value": EVERYONE}, @@ -1096,7 +1072,9 @@ WM.module('wm.widgets.base', []) "itemlabel": {"type": "list", "options": [""], "datasetfilter" : "terminals"}, "itemlink": {"type": "list", "options": [""], "datasetfilter" : "terminals"}, "itembadge": {"type": "list", "options": [""], "datasetfilter" : "terminals"}, + "itemaction": {"type": "list", "options": [""], "datasetfilter" : "terminals"}, "itemchildren": {"type": "list", "options": [""], "datasetfilter" : "objects"}, + "userrole": {"type": "list", "options": [""], "datasetfilter" : "terminals"}, "addchild": {"hidelabel": true, "options": [{'label': 'Anchor', 'widgettype': 'wm-anchor', 'defaults': {'wm-anchor': {'iconclass': 'wi wi-file', 'margin': ''} } }, {'label': 'Menu', 'widgettype': 'wm-menu', 'defaults': {'wm-menu': {'iconclass': 'wi wi-file', 'type': 'anchor'} } }, {'label': 'Popover', 'widgettype': 'wm-popover', 'defaults': {'wm-popover': {'iconclass': 'wi wi-file'} } }, {'label': 'Button', 'widgettype': 'wm-button', 'defaults': {'wm-button': {'iconclass': 'wi wi-file'} } }], "widget": "add-widget"}, "selecteditem": {"type": "object", "bindable": "in-out-bound", "show": false, "widget": "string", "getTypeFrom": "dataset"}, "onSelect": {"type": "event", "options": widgetEventOptions, "widget": "eventlist"}, @@ -1140,9 +1118,9 @@ WM.module('wm.widgets.base', []) "datavalue": {"type": "string, object", "widget": "string", "bindable": "in-out-bound", "getTypeFrom": "dataset"}, "scopedataset": {"type": "string"}, "dataset": {"type": "array, object", "bindable": "in-bound", "widget": "string"}, - "searchkey": {"type": "list", "options": [""], "datasetfilter": "terminals"}, - "displaylabel": {"type": "list", "options": [""], "datasetfilter": "terminals"}, - "displayimagesrc": {"type": "list", "options": [""], "datasetfilter": "terminals"}, + "searchkey": {"type": "string", "widget": "select-all", "datasetfilter" : "terminals"}, + "displaylabel": {"type": "string", "widget": "list", "options": [""], "bindable": "in-bound", "bindonly": "expression", "datasetfilter" : "terminals"}, + "displayimagesrc": {"type": "string", "widget": "list", "options": [""], "bindable": "in-bound", "bindonly": "expression", "datasetfilter" : "terminals"}, "datafield": {"type": "list", "options": ["All Fields"], "value": "All Fields", "datasetfilter" : "terminals", "allfields" : true}, "defaultview": {"type": "select-by-object", "options": [{"label": "action-view", "value": "actionview"}, {"label": "search-view", "value": "searchview"}], "value": "actionview", "displayfield": "label", "datafield": "value" }, "query": {"type": "string", "bindable": "in-out-bound", "value": ""}, @@ -1184,7 +1162,7 @@ WM.module('wm.widgets.base', []) }, 'wm.layouts.panel': { "height": {"type": "string", "pattern": dimensionRegex}, - "title": {"type": "string", "value": "Title", "bindable": "in-bound", "showPrettyExprInDesigner": true}, + "title": {"type": "string", "bindable": "in-bound", "showPrettyExprInDesigner": true}, "subheading": {"type": "string", "bindable": "in-bound", "showPrettyExprInDesigner": true}, "iconclass": {"type": "string", "widget": "select-icon", "bindable": "in-out-bound", "pattern": classRegex, "label": 'Title Icon Class'}, "iconurl": {"type": "string", "bindable": "in-bound"}, @@ -1297,7 +1275,7 @@ WM.module('wm.widgets.base', []) "height": {"type": "string", "pattern": dimensionRegex}, "width": {"type": "string", "pattern": dimensionRegex}, "show": {"type": "boolean", "value": true, "bindable": "in-bound"}, - "deferload": {"type": "boolean", "value": false, "show": false, "ignoreGetterSetters": true}, + "deferload": {"type": "boolean", "value": false, "show": false}, "insert": {"type": "toolbar", "actions": [{'action': 'addrow', 'label': 'LABEL_PROPERTY_ADDROW', 'icon': 'add-row'}]}, "columns": {"type": "list", "options": ["1", "2", "3", "4", "6", "12"]}, "backgroundcolor": {"type": "string", "widget": "color", "show": false}, @@ -1353,7 +1331,7 @@ WM.module('wm.widgets.base', []) }, 'wm.layouts.view': { "show": {"type": "boolean", "value": true, "bindable" : "in-out-bound"}, - "deferload": {"type": "boolean", "value": false, "show": false, "ignoreGetterSetters": true}, + "deferload": {"type": "boolean", "value": false, "show": false}, "viewgroup": {"type": "string", "value": "default"}, "animation": {"type": "list", "options": animationOptions} }, @@ -1367,7 +1345,7 @@ WM.module('wm.widgets.base', []) "method": {"type": "list", "options": ["post", "put", "delete"], "showindesigner": true}, "height": {"type": "string", "pattern": dimensionRegex}, "width": {"type": "string", "pattern": dimensionRegex}, - "dataset": {"type": "array, object", "widget": "string", "disabled": true, "bindable": "in-bound"}, + "dataset": {"type": "array, object", "widget": "string", "bindable": "in-bound"}, "captionsize": {"type": "string", "showindesigner": true}, "iconclass": {"type": "string", "widget": "select-icon", "bindable": "in-bound", "pattern": classRegex, "label": 'Title Icon Class'}, "formdata": {"type": "object", "bindable": "in-bound", "widget": "string", "getTypeFrom": "dataset"}, @@ -1490,6 +1468,7 @@ WM.module('wm.widgets.base', []) "filternullrecords": {"type": "boolean", "value": true}, "nodatamessage": {"type": "string", "value": "No data found.", "bindable": "in-bound", "showindesigner": true}, "loadingdatamsg": {"type": "string", "value": "Loading...", "bindable": "in-bound", "showindesigner": true}, + "loadingicon": {"type": "string", "widget": "select-icon", "bindable": "in-bound", "value": "fa fa-spinner fa-spin", "pattern": classRegex}, "deletemessage": {"type": "string", "value": "Record deleted successfully", "bindable": "in-bound", "show": true, "showindesigner": true}, "errormessage": {"type": "string", "value": "", "bindable": "in-bound", "showindesigner": true}, "insertmessage": {"type": "string", "value": "Record added successfully", "bindable": "in-bound", "showindesigner": true}, @@ -1586,7 +1565,8 @@ WM.module('wm.widgets.base', []) "showindevice": {"type": "select-all", "options": showInDeviceOptions, "value": "all", "displaytype": 'inline'}, "animation": {"type": "list", "options": animationOptions}, "shortcutkey": {"type": "string"}, - "class": {"type": "string", "pattern": classRegex, "widget": "list-picker", "options": ["h1", "h2", "h3", "h4", "h5", "h6", "btn-primary", "btn-success", "btn-info", "btn-warning", "btn-danger", "btn-lg", "btn-sm", "btn-xs", "btn-link", "text-ellipsis", "text-left", "text-right", "text-center", "text-muted", "text-primary", "text-success", "text-info", "text-warning", "text-danger", "vertical-align-top", "vertical-align-middle", "vertical-align-bottom", "lead", "badge"]} + "class": {"type": "string", "pattern": classRegex, "widget": "list-picker", "options": ["h1", "h2", "h3", "h4", "h5", "h6", "btn-primary", "btn-success", "btn-info", "btn-warning", "btn-danger", "btn-lg", "btn-sm", "btn-xs", "btn-link", "text-ellipsis", "text-left", "text-right", "text-center", "text-muted", "text-primary", "text-success", "text-info", "text-warning", "text-danger", "vertical-align-top", "vertical-align-middle", "vertical-align-bottom", "lead", "badge"]}, + "horizontalalign": {"type": "string", "options": ["left", "center", "right"], "widget": "icons-align", "show": false} }, "wm.popover": { "contentsource": {"type": "list", "options": ['inline', 'partial'], value: "partial"}, @@ -1598,6 +1578,7 @@ WM.module('wm.widgets.base', []) "popoverheight" : {"type": "string"}, "popoverarrow" : {"type": "boolean", "value" : true}, "popoverautoclose": {"type": "boolean", "value" : true, "show": false}, + "interaction": {"type": "select-by-object", "options": [{'label': 'Click', 'value': 'click'}, {'label': 'Hover', 'value': 'hover'}, {'label': 'Click and Hover', 'value': 'default'}], value: "click"}, "popoverplacement": {"type": "select-by-object", "options": popoverOptions, "value": "bottom"}, "title": {"type": "string", "bindable": "in-bound"}, "onShow": {"type": "event", "options": widgetEventOptions, "widget": "eventlist"}, @@ -1619,7 +1600,9 @@ WM.module('wm.widgets.base', []) "addchild": {"hidelabel": true, "options": [{"label": "Add Accordion Pane", "widgettype": "wm-accordionpane"}], "widget": "add-widget"}, "closeothers": { "type": "boolean", "value": true}, "height": {"type": "string", "pattern": dimensionRegex}, - "tabindex": {"type": "number", "value": "0"} + "tabindex": {"type": "number", "value": "0"}, + "defaultpaneindex": {"type": "number", "value": 0, "bindable": "in-bound"}, + "onChange": {"type": "event", "options": widgetEventOptions, "widget": "eventlist"} }, "wm.accordionpane": { @@ -1632,7 +1615,7 @@ WM.module('wm.widgets.base', []) "badgevalue": {"type": "string", "bindable": "in-out-bound"}, "badgetype": {"type": "string", "widget": "list", "options": ["default", "primary", "success", "info", "warning", "danger"], "value": "default", "bindable": "in-out-bound"}, "tabindex": {"type": "number", "value": "0"}, - "isdefaultpane": {"type": "boolean", "bindable": "in-bound"}, + "isdefaultpane": {"type": "boolean", "bindable": "in-bound", "show": false}, //Deprecated property "padding": {"type": "string", "widget": "box-model"}, "backgroundcolor": {"type": "string", "widget": "color"}, "color": {"type": "string", "hidelabel": true, "widget": "color"}, @@ -1646,6 +1629,7 @@ WM.module('wm.widgets.base', []) "datavalue": {"type": "string", value: "", "bindable": "in-out-bound"}, "showpreview": {"type": "boolean", "value": false}, "placeholder": {"type": "string", "bindable": "in-bound"}, + "htmlcontent": {"type": "string", "bindable": "out-bound"}, "tabindex": {"type": "number", "value": "0"}, "scopedatavalue": {"type": "string"}, "onChange": {"type": "event", "options": widgetEventOptions, "widget": "eventlist"}, @@ -1660,7 +1644,9 @@ WM.module('wm.widgets.base', []) "transition": {"type": "list", "options": ["none", "slide"], "value": "none"}, "name": {"type": "string", "pattern": nameRegex, "maxlength": 32}, "height": {"type": "string", "pattern": dimensionRegex}, - "horizontalalign": {"type": "string", "options": ["left", "center", "right"], "widget": "icons-align"} + "horizontalalign": {"type": "string", "options": ["left", "center", "right"], "widget": "icons-align"}, + "defaultpaneindex": {"type": "number", "value": 0, "bindable": "in-bound"}, + "onChange": {"type": "event", "options": widgetEventOptions, "widget": "eventlist"} }, "wm.tab": { @@ -1672,8 +1658,10 @@ WM.module('wm.widgets.base', []) "onDeselect": {"type": "event", "options": widgetEventOptions, "widget": "eventlist"}, "title": {"type": "string", "value": "Tab Title", "bindable": "in-bound", "showPrettyExprInDesigner": true}, "paneicon": {"type": "string", "widget": "select-icon", "bindable": "in-bound", "pattern": classRegex, "label": 'Title Icon Class'}, - "isdefaulttab": {"type": "boolean", "bindable": "in-bound"}, - "tabindex": {"type": "number", "value": "0"} + "isdefaulttab": {"type": "boolean", "bindable": "in-bound", "show": false}, //Deprecated property + "tabindex": {"type": "number", "value": "0"}, + "badgevalue": {"type": "string", "bindable": "in-out-bound"}, + "badgetype": {"type": "string", "widget": "list", "options": ["default", "primary", "success", "info", "warning", "danger"], "value": "default", "bindable": "in-out-bound"} }, "wm.wizard": { "addchild": {"hidelabel": true, "options": [{"label": "Add Step", "widgettype": "wm-wizardstep"}], "widget": "add-widget"}, @@ -1689,13 +1677,16 @@ WM.module('wm.widgets.base', []) "wm.wizardstep": { "title": {"type": "string", "value": "Step Title", "bindable": "in-bound", "showPrettyExprInDesigner": true}, "disablenext": {"type": "boolean", "value": false, "show": false}, + "disableprevious": {"type": "boolean", "value": false, "show": false}, + "disabledone": {"type": "boolean", "value": false, "show": false}, "enabledone": {"type": "boolean", "value": false, "show": false}, "enableskip": {"type": "boolean", "value": false, "bindable": "in-bound"}, "iconclass": {"type": "string", "widget": "select-icon", "bindable": "in-out-bound", "pattern": classRegex, "label": 'Title Icon Class'}, "onNext": {"type": "event", "options": widgetEventOptions, "widget": "eventlist"}, "onPrev": {"type": "event", "options": widgetEventOptions, "widget": "eventlist"}, "onLoad": {"type": "event", "options": widgetEventOptions, "widget": "eventlist"}, - "onSkip": {"type": "event", "options": widgetEventOptions, "widget": "eventlist"} + "onSkip": {"type": "event", "options": widgetEventOptions, "widget": "eventlist"}, + "loadmode" : {"type" : "list", "show": false, "value" : "after-select"} }, "wm.carousel" : { "addchild": {"hidelabel": true, "options": [{"label": "Add Carousel", "widgettype": "wm-carousel-content"}], "widget": "add-widget"}, @@ -1767,7 +1758,7 @@ WM.module('wm.widgets.base', []) "showcount": {"type": "boolean", "show": false}, "disableitem": {"type": "boolean", "bindable": "in-bound", "value": false}, "ondemandmessage": {"type": "string", "bindable": "in-bound", "showPrettyExprInDesigner": true, "value": "Load More"}, - "loadingicon": {"type": "string", "widget": "select-icon", "bindable": "in-bound", "value": "", "pattern": classRegex}, + "loadingicon": {"type": "string", "widget": "select-icon", "bindable": "in-bound", "value": "fa fa-circle-o-notch", "pattern": classRegex}, "paginationclass": {"type": "string", "pattern": classRegex, "widget": "list-picker", "options": ["pagination-sm", "pagination-lg", "btn-default", "btn-primary", "btn-info", "btn-warning", "btn-success", "btn-danger", "btn-inverse", "btn-lg", "btn-sm", "btn-xs", "btn-raised", "btn-fab", "btn-link", "btn-transparent", "jumbotron"]} }, "wm.medialist": { @@ -1815,7 +1806,7 @@ WM.module('wm.widgets.base', []) "datavalue": {"type": "string, object", "widget": "string", "bindable": "in-out-bound", "getTypeFrom": "dataset"}, "scopedataset": {"type": "string"}, "query": {"type": "string", "bindable": "out-bound"}, - "searchkey": {"type": "string", "widget": "select-all", "bindable": "in-bound", "datasetfilter" : "terminals"}, + "searchkey": {"type": "string", "widget": "select-all", "datasetfilter" : "terminals"}, "displaylabel": {"type": "string", "widget": "list", "options": [""], "bindable": "in-bound", "bindonly": "expression", "datasetfilter" : "terminals"}, "displayimagesrc": {"type": "string", "widget": "list", "options": [""], "bindable": "in-bound", "bindonly": "expression", "datasetfilter" : "terminals"}, "datafield": {"type": "list", "options": ["All Fields"], "value": "All Fields", "datasetfilter" : "terminals", "allfields" : true}, @@ -1980,10 +1971,10 @@ WM.module('wm.widgets.base', []) "hint": {"type": "string", "bindable": "in-bound"}, "minvalue": {"type": "number", "value": 0, "bindable": "in-bound"}, "maxvalue": {"type": "number", "value": 100, "bindable": "in-bound"}, - "datavalue": {"type": "number, string", "value": 30, "bindable": "in-out-bound", "widget": "string"}, + "datavalue": {"type": "number, string", "bindable": "in-out-bound", "widget": "string"}, "dataset": {"type": "array, object", "bindable": "in-bound", "widget": "string"}, "pollinterval": {"type": "number"}, - "displayformat": {"type": "list", "options": ["percentage", "absolute"], "value": "percentage"}, + "displayformat": {"type": "list", "options": ["9", "9.9", "9.99", "9.999", "9%", "9.9%", "9.99%", "9.999%"]}, "captionplacement": {"type": "list", "options": ["inside", "hidden"], "value": "hidden"}, "type": {"type": "list", "options": ["default", "default-striped", "success", "success-striped", "info", "info-striped", "warning", "warning-striped", "danger", "danger-striped"], "value": "default", "bindable": "in-bound"}, @@ -2033,16 +2024,16 @@ WM.module('wm.widgets.base', []) {"name": "valuedisplay", "properties": ["datepattern", "timepattern", "hourstep", "minutestep", "limit"], "parent": "properties"}, {"name": "output", "properties": ["outputformat"], "parent": "properties"}, {"name": "eventsdata", "properties": ["eventtitle", "eventstart", "eventend", "eventallday", "eventclass"], "parent": "properties"}, - {"name": "actions", "properties": ["actions", "itemlabel", "itemicon", "itemlink", "itembadge", "itemchildren"], "parent": "properties"}, + {"name": "actions", "properties": ["actions", "itemlabel", "itemicon", "itemlink", "itemaction", "userrole", "itembadge", "itemchildren"], "parent": "properties"}, {"name": "xaxis", "properties": ["xaxisdatakey"], "parent": "properties"}, {"name": "yaxis", "properties": ["yaxisdatakey"], "parent": "properties"}, {"name": "zaxis", "properties": ["bubblesize"], "parent": "properties"}, {"name": "validation", "properties": ["required", "validationmessage", "regexp", "mindate", "maxdate", "excludedays", "excludedates", "novalidate", "maxchars"], "parent": "properties"}, - {"name": "behavior", "properties": ["method", "action", "enctype", "target", "defaultview", "defaultmode", "pollinterval", "radiogroup", "viewgroup", "showweeks", "showbuttonbar", "autofocus", "readonly", "ignoreparentreadonly", "editmode", "scrolldelay", "scrollamount", "direction", + {"name": "behavior", "properties": ["method", "action", "enctype", "target", "defaultview", "defaultmode", "defaultpaneindex", "pollinterval", "radiogroup", "viewgroup", "showweeks", "showbuttonbar", "autofocus", "readonly", "ignoreparentreadonly", "editmode", "scrolldelay", "scrollamount", "direction", "multiple", "maxsize", "allowonlyselect", "enablereorder", "fileuploadmessage", "mode", "show", "deferload", "hideclose", "calendartype", "controls", "view", "disabled", "disableitem", "pagesize", "dynamicslider", "selectionclick", "closeothers", "collapsible", "showcount", "enablefullscreen", "lock", "freeze", "autoscroll", "closable", "showactions", "expanded", "destroyable", "showDirtyFlag", "link", "linktarget", - "uploadpath", "contenttype", "origin", "destination", "maxfilesize", "isdefaulttab", "disablenext", "enabledone", "enableskip", "cancelable", "isdefaultpane", "autocomplete", "showpreview", "autoplay", "loop", "muted", - "xpadding", "ypadding", "popoverplacement", "popoverarrow", "popoverautoclose", "autoclose", "transition", "animation", "animateitems", "animationinterval", "leftnavpaneliconclass", "backbutton", "backbuttoniconclass", "backbuttonlabel", "searchbutton", + "uploadpath", "contenttype", "origin", "destination", "maxfilesize", "isdefaulttab", "disablenext", "disableprevious", "disabledone", "enabledone", "enableskip", "cancelable", "isdefaultpane", "autocomplete", "showpreview", "autoplay", "loop", "muted", + "xpadding", "ypadding", "popoverplacement", "popoverarrow", "popoverautoclose", "interaction", "autoclose", "transition", "animation", "animateitems", "animationinterval", "leftnavpaneliconclass", "backbutton", "backbuttoniconclass", "backbuttonlabel", "searchbutton", "morebuttoniconclass", "menuiconclass", "morebuttonlabel", "capturetype", "loadmode", "loaddelay", "selectionlimit", "showcaptions", "multiselect", "radioselect", "enablesort", "enablecolumnselection", "gridfirstrowselect", "selectfirstitem", "enableemptyfilter", "autoupdate", "displayformat", "captionplacement", "updateon", "updatedelay", "actionlink", "actiontitle", "offline", "encodeurl", "keyboard"], "parent": "properties"}, {"name": "navigation", "properties": ["navigation", "shownavigation", "showrecordcount", "navigationalign", "ondemandmessage"], "parent": "properties"}, {"name": "searchproperties", "properties": ["searchbuttoniconclass", "searchbuttonlabel", "searchplaceholder"], "parent": "properties"}, diff --git a/src/main/webapp/scripts/modules/widgets/base/initWidget.js b/src/main/webapp/scripts/modules/widgets/base/initWidget.js index a38739120..27b30f5a2 100644 --- a/src/main/webapp/scripts/modules/widgets/base/initWidget.js +++ b/src/main/webapp/scripts/modules/widgets/base/initWidget.js @@ -201,7 +201,11 @@ WM.module('wm.widgets.base') //removing the data-evaluated attribute. If the evaluation value is correct this property is not required in design mode. $el.removeAttr(dataAttrKey); $is[key + '__updateFromWatcher'] = true; - if (WM.isDefined(newVal) && newVal !== null && newVal !== '') { + //For dataset widgets, show default dataset values, if dataset is not available in studio mode + if ($is.widgetid && propDetails.showPrettyExprInDesigner && propDetails.defaultvalue && (_.isEmpty(newVal) || (newVal.data && _.isEmpty(newVal.data)))) { + value = propDetails.defaultvalue; + $el.attr(dataAttrKey, ''); + } else if (WM.isDefined(newVal) && newVal !== null && newVal !== '') { //Check if "newVal" is a Pageable object. if (WM.isObject(newVal) && Utils.isPageable(newVal)) { // Check if the scope is configured to accept Pageable objects. diff --git a/src/main/webapp/scripts/modules/widgets/base/pageContainer.js b/src/main/webapp/scripts/modules/widgets/base/pageContainer.js index 159302487..8e541bba6 100644 --- a/src/main/webapp/scripts/modules/widgets/base/pageContainer.js +++ b/src/main/webapp/scripts/modules/widgets/base/pageContainer.js @@ -60,7 +60,6 @@ WM.module('wm.widgets.base') if (CONSTANTS.isRunMode && iScope._widgettype === 'wm-popover' && iScope.contentsource === 'partial') { //Class will be used to set popover height width to avoid flickering issue iScope._popoverOptions.customclass = 'popover_' + iScope.$id + '_' + partialName + '_' + _.toLower($rootScope.activePageName); - $rootScope.$emit('reset-partial-variables', partialName); } /* append the pageContentMarkup to original markup, to compile it manually*/ @@ -101,10 +100,10 @@ WM.module('wm.widgets.base') } else { /* compile */ target.html($compile(partialMarkup)(scope)); + $timeout(function () { + Utils.triggerFn(iScope.onLoad, {$isolateScope: iScope}); + }, undefined, false); } - $timeout(function () { - Utils.triggerFn(iScope.onLoad, {$isolateScope: iScope}); - }, undefined, false); } else { return; } diff --git a/src/main/webapp/scripts/modules/widgets/basic/chart/chart.js b/src/main/webapp/scripts/modules/widgets/basic/chart/chart.js index a0c8d6334..04b632eda 100644 --- a/src/main/webapp/scripts/modules/widgets/basic/chart/chart.js +++ b/src/main/webapp/scripts/modules/widgets/basic/chart/chart.js @@ -23,7 +23,7 @@ WM.module('wm.widgets.basic') '
    ' ); }]) - .directive('wmChart', function (PropertiesFactory, $templateCache, $rootScope, WidgetUtilService, CONSTANTS, QueryBuilder, Utils, ChartService) { + .directive('wmChart', function (PropertiesFactory, $templateCache, $rootScope, WidgetUtilService, CONSTANTS, Utils, ChartService, DatabaseService) { 'use strict'; var widgetProps = PropertiesFactory.getPropertiesOf('wm.chart', ['wm.base']), // properties of the respective chart type @@ -82,6 +82,14 @@ WM.module('wm.widgets.basic') 'showlegend' : true, 'title' : true, 'subheading' : true + }, + // Getting the relevant aggregation function based on the selected option + aggregationFnMap = { + 'average' : 'AVG', + 'count' : 'COUNT', + 'maximum' : 'MAX', + 'minimum' : 'MIN', + 'sum' : 'SUM' }; // Configuring the properties panel based on the type of the chart chosen @@ -163,34 +171,17 @@ WM.module('wm.widgets.basic') function isDataFilteringEnabled(scope) { /*Query need to be triggered if any of the following cases satisfy * 1. Group By and aggregation both chosen - * 2. Group By alone with atleast two columns chosen, one of which is x axis - * 3. Only Order By is chosen + * 2. Only Order By is chosen * */ - return isAggregationEnabled(scope) || (isGroupByEnabled(scope.groupby) && scope.isVisuallyGrouped) || scope.orderby; - } - - //Gets the value by parsing upto the leaf node - function getLeafNodeVal(key, dataObj) { - var keys = key.split('.'), - data = dataObj, - i; - for (i = 0; i < keys.length; i += 1) { - if (data) { - data = data[keys[i]]; - } else { //If value becomes undefined then acceess the key directly - data = dataObj[key]; - break; - } - } - return data; + return isAggregationEnabled(scope) || (!scope.isVisuallyGrouped && scope.orderby); } /*Charts like Line,Area,Cumulative Line does not support any other datatype other than integer unlike the column and bar.It is a nvd3 issue. Inorder to support that this is a fix*/ function getxAxisVal(scope, dataObj, xKey, index) { - var value = getLeafNodeVal(xKey, dataObj); + var value = _.get(dataObj, xKey); //If x axis is other than number type then add indexes if (ChartService.isLineTypeChart(scope.type) && !Utils.isNumberType(scope.xAxisDataType)) { //Verification to get the unique data keys @@ -273,7 +264,7 @@ WM.module('wm.widgets.basic') //Returns the single data point based on the type of the data chart accepts function valueFinder(scope, dataObj, xKey, yKey, index, shape) { var xVal = getxAxisVal(scope, dataObj, xKey, index), - value = getLeafNodeVal(yKey, dataObj), + value = _.get(dataObj, yKey), yVal = parseFloat(value) || value, dataPoint = {}, size = parseFloat(dataObj[scope.bubblesize]) || 2; @@ -311,7 +302,7 @@ WM.module('wm.widgets.basic') scope.xDataKeyArr = []; //Plotting the chart with sample data when the chart dataset is not bound if (!scope.binddataset) { - scope.xDataKeyArr = ['01-01-2001', '01-01-2002', '01-01-2003']; + scope.xDataKeyArr = ChartService.getDateList(); if (CONSTANTS.isStudioMode) { scope.showContentLoadError = true; scope.errMsg = $rootScope.locale.MESSAGE_INFO_SAMPLE_DATA; @@ -373,110 +364,53 @@ WM.module('wm.widgets.basic') return datum; } - // Getting the relevant aggregation function based on the selected option - function getAggregationFunction(option) { - switch (option) { - case 'average': - return 'AVG'; - case 'count': - return 'COUNT'; - case 'maximum': - return 'MAX'; - case 'minimum': - return 'MIN'; - case 'sum': - return 'SUM'; - default: - return ''; - } + //Returns orderby columns and their orders in two separate arrays + function getLodashOrderByFormat(orderby) { + var orderByColumns = [], + orders = [], + columns; + _.forEach(_.split(orderby, ','), function (col) { + columns = _.split(col, ':'); + orderByColumns.push(columns[0]); + orders.push(columns[1]); + }); + return { + 'columns' : orderByColumns, + 'orders' : orders + }; } + //Constructing the grouped data based on the selection of orderby, x & y axis - function getGroupedData(scope, queryResponse, groupingColumn) { + function getVisuallyGroupedData(scope, queryResponse, groupingColumn) { var chartData = [], groupData = {}, groupValues = [], - groupKey, - index = 0, - i; + orderByDetails; scope.xDataKeyArr = []; - - while (queryResponse.length !== 0) { - groupKey = queryResponse[queryResponse.length - 1][groupingColumn]; - //Data should be in ascending order of 'x', since there is tooltips issue incase of line chart - groupValues.push(valueFinder(scope, queryResponse[queryResponse.length - 1], scope.xaxisdatakey, scope.yaxisdatakey, 0)); - queryResponse.splice(queryResponse.length - 1, 1); - for (i = queryResponse.length - 1; i >= 0; i -= 1) { - /*Checking if the new column groupKey is same as the chosen groupKey - Then pushing the data - Then splicing the data since it is already pushed */ - if (groupKey === queryResponse[i][groupingColumn]) { - index += 1; - //Data should be in ascending order of 'x', since there is tooltips issue incase of line chart - groupValues.push(valueFinder(scope, queryResponse[i], scope.xaxisdatakey, scope.yaxisdatakey, index)); - queryResponse.splice(i, 1); - } - } - - //Pushing the data with groupKey and values + queryResponse = _.orderBy(queryResponse, _.split(scope.groupby, ',')); + if (scope.orderby) { + orderByDetails = getLodashOrderByFormat(scope.orderby); + queryResponse = _.orderBy(queryResponse, orderByDetails.columns, orderByDetails.orders); + } + queryResponse = _.groupBy(queryResponse, groupingColumn); + _.forEach(queryResponse, function (values, groupKey) { + groupValues = []; + _.forEachRight(values, function (value, index) { + groupValues.push(valueFinder(scope, value, scope.xaxisdatakey, scope.yaxisdatakey, index)); + }); groupData = { key : groupKey, values : groupValues }; chartData.push(groupData); - groupValues = []; - index = 0; - } - return chartData; - } - - //Construct orderby expression - function getOrderbyExpression(orderby) { - var orderbyCols = (orderby ? orderby.replace(/:/g, ' ') : '').split(','), - trimmedCols = ''; - orderbyCols = orderbyCols.map(function (col) { - return col.trim(); }); - trimmedCols = orderbyCols.join(); - return trimmedCols; + return chartData; } - //Replacing the '.' by the '_' because '.' is not supported in the alias names + //Replacing the '.' by the '$' because '.' is not supported in the alias names function getValidAliasName(aliasName) { - return aliasName ? aliasName.replace(/\./g, '_') : null; - } - - // Returns the columns that are to be fetched in the query response - function getQueryColumns(scope) { - var columns = [], - groupbyColumns = scope.groupby && scope.groupby !== NONE ? scope.groupby.split(',') : [], - yAxisKeys = scope.yaxisdatakey ? scope.yaxisdatakey.split(',') : [], - expr, - xAxisKey = scope.xaxisdatakey, - aggColumn = scope.aggregationcolumn || [], - requiredColumns = []; - - /*Merge all columns and filter unique ones and remove empty columns ('', undefined) - * 1. X Axis - * 2. Y Axis - * 3. Aggregation - * 4. Group By - * */ - requiredColumns = _.compact(_.uniq(_.concat(xAxisKey, yAxisKeys, groupbyColumns, aggColumn))); - - // adding aggregation column, if enabled - if (isAggregationEnabled(scope)) { - columns.push(getAggregationFunction(scope.aggregation) + '(' + aggColumn + ') AS ' + getValidAliasName(aggColumn)); - } - - //Remove aggregation column since it is already added - requiredColumns = _.without(requiredColumns, aggColumn); - - _.forEach(requiredColumns, function (column) { - expr = column + ' AS ' + getValidAliasName(column); - columns.push(expr); - }); - return columns; + return aliasName ? aliasName.replace(/\./g, '$') : null; } /*Decides whether the data should be visually grouped or not @@ -539,15 +473,15 @@ WM.module('wm.widgets.basic') } //Function to get the aggregated data after applying the aggregation & group by or order by operations. - function getAggregatedData(scope, element, groupingDetails, callback) { - var query, - variableName, + function getAggregatedData(scope, element, callback) { + var variableName, variable, - columns, yAxisKeys = scope.yaxisdatakey ? scope.yaxisdatakey.split(',') : [], - orderbyexpression = getOrderbyExpression(scope.orderby), - groupbyExpression = groupingDetails.expression, - elScope = element.scope(); + elScope = element.scope(), + data = {}, + sortExpr = _.replace(scope.orderby, /:/g, ' '), + orderByColumn = _.first(_.split(sortExpr, ' ')), + columns = []; //Returning if the data is not yet loaded if (!scope.chartData) { @@ -566,27 +500,36 @@ WM.module('wm.widgets.basic') if (!variable) { return; } - columns = getQueryColumns(scope); - query = QueryBuilder.getQuery({ - 'tableName': variable.type, - 'columns': columns, - 'filterFields': scope.filterFields || variable.filterFields, - 'groupby': groupbyExpression, - 'orderby': orderbyexpression - }); + columns = _.concat(columns, data.groupByFields, [scope.aggregationcolumn]); + if (_.includes(columns, orderByColumn)) { + sortExpr = getValidAliasName(sortExpr); + } + if (isGroupByEnabled(scope.groupby)) { + data.groupByFields = _.split(scope.groupby, ','); + } + if (isAggregationEnabled(scope)) { + data.aggregations = [ + { + "field": scope.aggregationcolumn, + "type": _.toUpper(scope.aggregation), + "alias": getValidAliasName(scope.aggregationcolumn) + } + ]; + } //Execute the query. - QueryBuilder.executeQuery({ - 'databaseName': variable.liveSource, - 'query': query, - 'page': 1, - 'size': variable.maxResults || 500, - 'nativeSql': false + DatabaseService.executeAggregateQuery({ + 'dataModelName' : variable.liveSource, + 'entityName' : variable.type, + 'page' : 1, + 'size' : variable.maxResults || 500, + 'sort' : sortExpr, + 'url' : $rootScope.project.deployedUrl, + 'data' : data }, function (response) { //Transform the result into a format supported by the chart. var chartData = [], aggregationAlias = getValidAliasName(scope.aggregationcolumn), - visualGroupingColumnAlias = groupingDetails.visualGroupingColumn ? getValidAliasName(groupingDetails.visualGroupingColumn) : '', xAxisAliasKey = getValidAliasName(scope.xaxisdatakey), yAxisAliasKeys = []; @@ -599,25 +542,20 @@ WM.module('wm.widgets.basic') // Set the response in the chartData based on 'aggregationColumn', 'xAxisDataKey' & 'yAxisDataKey'. if (isAggregationEnabled(scope)) { obj[scope.aggregationcolumn] = data[aggregationAlias]; + obj[scope.aggregationcolumn] = _.get(data, aggregationAlias) || _.get(data, scope.aggregationcolumn); } - if (visualGroupingColumnAlias) { - obj[groupingDetails.visualGroupingColumn] = data[visualGroupingColumnAlias]; - } - - obj[scope.xaxisdatakey] = data[xAxisAliasKey]; - yAxisKeys.forEach(function (yAxisKey) { - yAxisAliasKeys.push(getValidAliasName(yAxisKey)); - }); + obj[scope.xaxisdatakey] = _.get(data, xAxisAliasKey) || _.get(data, scope.xaxisdatakey); yAxisKeys.forEach(function (yAxisKey, index) { obj[yAxisKey] = data[yAxisAliasKeys[index]]; + obj[yAxisKey] = _.get(data, yAxisAliasKeys[index]) || _.get(data, yAxisKey); }); chartData.push(obj); }); - scope.chartData = groupingDetails.isVisuallyGrouped ? getGroupedData(scope, chartData, groupingDetails.visualGroupingColumn) : chartData; + scope.chartData = chartData; Utils.triggerFn(callback); }, function () { @@ -879,12 +817,14 @@ WM.module('wm.widgets.basic') // get the chart object chart = ChartService.initChart(scope, xDomainValues, yDomainValues, null, !scope.binddataset); - // changing the default no data message - d3.select('#wmChart' + scope.$id + ' svg') - .datum(chartData) - .call(chart); - postPlotProcess(scope, element, chart); - return chart; + if (WM.isArray(chartData)) { + // changing the default no data message + d3.select('#wmChart' + scope.$id + ' svg') + .datum(chartData) + .call(chart); + postPlotProcess(scope, element, chart); + return chart; + } } // Plotting the chart with set of the properties set to it @@ -938,13 +878,17 @@ WM.module('wm.widgets.basic') var groupingDetails = getGroupingDetails(scope); //If aggregation/group by/order by properties have been set, then get the aggregated data and plot the result in the chart. if (scope.binddataset && scope.isLiveVariable && (scope.filterFields || isDataFilteringEnabled(scope))) { - getAggregatedData(scope, element, groupingDetails, function () { + getAggregatedData(scope, element, function () { plotChart(scope, element); }); } else { //Else, simply plot the chart. //In case of live variable resetting the aggregated data to the normal dataset when the aggregation has been removed if (scope.dataset && scope.dataset.data && scope.isLiveVariable) { scope.chartData = scope.dataset.data; + if (isGroupByEnabled(scope.groupby) && groupingDetails.isVisuallyGrouped) { + scope.chartData = getVisuallyGroupedData(scope, scope.chartData, groupingDetails.visualGroupingColumn); + } + } plotChart(scope, element); } @@ -1108,6 +1052,7 @@ WM.module('wm.widgets.basic') //Resetting the flag to false when the binding was removed if (!newVal && !scope.binddataset) { scope.isVisuallyGrouped = false; + scope.axisoptions = null; } variableObj = elScope.Variables && elScope.Variables[variableName]; @@ -1254,6 +1199,7 @@ WM.module('wm.widgets.basic') return { pre: function (iScope, $el, attrs) { iScope.widgetProps = attrs.widgetid ? Utils.getClonedObject(widgetProps) : widgetProps; + $el.removeAttr('title'); }, post: function (scope, element, attrs) { var handlers = [], diff --git a/src/main/webapp/scripts/modules/widgets/basic/chart/chartService.js b/src/main/webapp/scripts/modules/widgets/basic/chart/chartService.js index 75dea05d2..055fcf7f7 100644 --- a/src/main/webapp/scripts/modules/widgets/basic/chart/chartService.js +++ b/src/main/webapp/scripts/modules/widgets/basic/chart/chartService.js @@ -8,6 +8,7 @@ WM.module('wm.widgets.basic') 'use strict'; var chartTypes = ['Column', 'Line', 'Area', 'Cumulative Line', 'Bar', 'Pie', 'Donut', 'Bubble'], allShapes = ['circle', 'square', 'diamond', 'cross', 'triangle-up', 'triangle-down'], + dateList = ['01/01/2001', '01/01/2002', '01/01/2003'], themes = { 'Terrestrial': { colors: ['#1f77b4', '#aec7e8', '#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728', '#ff9896', '#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5'], @@ -225,19 +226,19 @@ WM.module('wm.widgets.basic') switch (dataType) { case 'jsonFormat': first_series = [ - {'x': '01-01-2001', 'y': 4000000}, - {'x': '01-01-2002', 'y': 1000000}, - {'x': '01-01-2003', 'y': 5000000} + {'x': '01/01/2001', 'y': 4000000}, + {'x': '01/01/2002', 'y': 1000000}, + {'x': '01/01/2003', 'y': 5000000} ]; second_series = [ - {'x': '01-01-2001', 'y': 3000000}, - {'x': '01-01-2002', 'y': 4000000}, - {'x': '01-01-2003', 'y': 7000000} + {'x': '01/01/2001', 'y': 3000000}, + {'x': '01/01/2002', 'y': 4000000}, + {'x': '01/01/2003', 'y': 7000000} ]; third_series = [ - {'x': '01-01-2001', 'y': 2000000}, - {'x': '01-01-2002', 'y': 8000000}, - {'x': '01-01-2003', 'y': 6000000} + {'x': '01/01/2001', 'y': 2000000}, + {'x': '01/01/2002', 'y': 8000000}, + {'x': '01/01/2003', 'y': 6000000} ]; data[0] = { values: first_series, @@ -701,14 +702,14 @@ WM.module('wm.widgets.basic') .showYAxis(propertyValueMap.showyaxis); //Setting the labels if they are specified explicitly or taking the axiskeys chosen - xaxislabel = propertyValueMap.xaxislabel || scope.xaxisdatakey || 'x caption'; - yaxislabel = propertyValueMap.yaxislabel || scope.yaxisdatakey || 'y caption'; + xaxislabel = propertyValueMap.xaxislabel || Utils.prettifyLabels(scope.xaxisdatakey) || 'x caption'; + yaxislabel = propertyValueMap.yaxislabel || Utils.prettifyLabels(scope.yaxisdatakey) || 'y caption'; //Adding the units to the captions if they are specified xaxislabel += propertyValueMap.xunits ? '(' + propertyValueMap.xunits + ')' : ''; yaxislabel += propertyValueMap.yunits ? '(' + propertyValueMap.yunits + ')' : ''; chart.xAxis - .axisLabel(Utils.prettifyLabels(xaxislabel)) + .axisLabel(xaxislabel) .axisLabelDistance(propertyValueMap.xaxislabeldistance) .staggerLabels(propertyValueMap.staggerlabels); @@ -717,7 +718,7 @@ WM.module('wm.widgets.basic') if (isLineTypeChart(scope.type)) { chart.xAxis.tickFormat(function (d) { //get the actual value - xAxisValue = scope.xDataKeyArr[d]; + xAxisValue = isPreview ? dateList[d - 1] : scope.xDataKeyArr[d - 1]; return getDateFormatedData(propertyValueMap.xdateformat, xAxisValue); }); } else { @@ -738,7 +739,7 @@ WM.module('wm.widgets.basic') } } chart.yAxis - .axisLabel(Utils.prettifyLabels(yaxislabel)) + .axisLabel(yaxislabel) .axisLabelDistance(propertyValueMap.yaxislabeldistance) .staggerLabels(propertyValueMap.staggerlabels) .tickFormat(function (d) { @@ -759,11 +760,6 @@ WM.module('wm.widgets.basic') colors = propertyValueMap.customcolors; } - //Converting strings to numbers - propertyValueMap.offsettop = Number(propertyValueMap.offsettop); - propertyValueMap.offsetright = Number(propertyValueMap.offsetright); - propertyValueMap.offsetbottom = Number(propertyValueMap.offsetbottom); - propertyValueMap.offsetleft = Number(propertyValueMap.offsetleft); showLegend = isShowLegend(propertyValueMap.showlegend); chart.showLegend(showLegend) .margin({top: propertyValueMap.offsettop, right: propertyValueMap.offsetright, bottom: propertyValueMap.offsetbottom, left: propertyValueMap.offsetleft}) @@ -790,6 +786,10 @@ WM.module('wm.widgets.basic') } } + function getDateList() { + return dateList; + } + this.isBarChart = isBarChart; this.isBubbleChart = isBubbleChart; this.isPieType = isPieType; @@ -811,5 +811,6 @@ WM.module('wm.widgets.basic') this.initProperties = initProperties; this.allShapes = allShapes; this.chartTypes = chartTypes; + this.getDateList = getDateList; } ]); diff --git a/src/main/webapp/scripts/modules/widgets/basic/dataNavigator/dataNavigator.js b/src/main/webapp/scripts/modules/widgets/basic/dataNavigator/dataNavigator.js index 4b540adf4..1310d3be8 100644 --- a/src/main/webapp/scripts/modules/widgets/basic/dataNavigator/dataNavigator.js +++ b/src/main/webapp/scripts/modules/widgets/basic/dataNavigator/dataNavigator.js @@ -117,7 +117,7 @@ WM.module("wm.widgets.basic") maxResults, currentPage, startIndex; - dataSize = WM.isArray(newVal) ? newVal.length : (newVal.data ? newVal.data.length : 1); + dataSize = WM.isArray(newVal) ? newVal.length : (newVal.data ? newVal.data.length : _.isEmpty(newVal) ? 0 : 1); maxResults = ($scope.pagingOptions && $scope.pagingOptions.maxResults) || dataSize; //For static variable, keep the current page. For other variables without pagination reset the page to 1 if (variable && variable.category === 'wm.Variable') { @@ -216,7 +216,8 @@ WM.module("wm.widgets.basic") maxResults, currentPage, variable, - variableOptions = {}; + variableOptions = {}, + elScope; //Store the data in __fullData. This is used for client side searching with out modifying the actual dataset. $scope.__fullData = newVal; /*Set the default value of the "result" property to the newVal so that the widgets bound to the data-navigator can have the dataSet set properly.*/ @@ -229,8 +230,11 @@ WM.module("wm.widgets.basic") if ($scope.binddataset.indexOf('bind:Variables.') !== -1) { $scope.variableName = $scope.binddataset.replace('bind:Variables.', ''); $scope.variableName = $scope.variableName.substr(0, $scope.variableName.indexOf('.')); - variable = $scope.variableName && $scope.navigatorElement.scope().Variables[$scope.variableName]; - variableOptions = variable._options || {}; + elScope = $scope.navigatorElement.scope(); + if ($scope.variableName && elScope) { + variable = elScope.Variables[$scope.variableName]; + variableOptions = variable._options || {}; + } } else if (newVal) { if (newVal.isBoundToFilter && newVal.widgetName) { $scope.isBoundToFilter = true; diff --git a/src/main/webapp/scripts/modules/widgets/basic/icon/icon.js b/src/main/webapp/scripts/modules/widgets/basic/icon/icon.js index 4d0a631f7..6be680528 100644 --- a/src/main/webapp/scripts/modules/widgets/basic/icon/icon.js +++ b/src/main/webapp/scripts/modules/widgets/basic/icon/icon.js @@ -5,12 +5,48 @@ WM.module('wm.widgets.basic') .run(['$templateCache', function ($templateCache) { 'use strict'; $templateCache.put('template/widget/icon.html', - '' + '' + + ' ' + + '' + + '' ); }]) .directive('wmIcon', ['PropertiesFactory', 'WidgetUtilService', 'Utils', function (PropertiesFactory, WidgetUtilService, Utils) { 'use strict'; - var widgetProps = PropertiesFactory.getPropertiesOf('wm.icon', ['wm.base']); + var widgetProps = PropertiesFactory.getPropertiesOf('wm.icon', ['wm.base']), + notifyFor = { + 'iconposition': true, + 'iconsize': true, + 'opacity': true, + 'iconclass': true, + 'hint': true, + 'color': true + }; + + //Define the property change handler. This function will be triggered when there is a change in the widget property + function propertyChangeHandler($el, $iNode, attrs, key, newVal, oldVal) { + switch (key) { + case 'iconposition': + $el.attr('icon-position', newVal); + break; + case 'iconsize': + $el[0].style.fontSize = newVal; + break; + case 'opacity': + $el[0].style.opacity = newVal; + break; + case 'color': + $iNode[0].style.color = newVal; + break; + case 'iconclass': + $iNode.removeClass(oldVal).addClass(newVal); + break; + case 'hint': + attrs.$set('title', newVal); + break; + } + } + return { 'restrict': 'E', 'scope' : {}, @@ -21,6 +57,10 @@ WM.module('wm.widgets.basic') scope.widgetProps = attrs.widgetid ? Utils.getClonedObject(widgetProps) : widgetProps; }, 'post': function (scope, element, attrs) { + WidgetUtilService.registerPropertyChangeListener( + propertyChangeHandler.bind(undefined, element, element.children().first(), attrs), + scope, + notifyFor); /* register the property change handler */ WidgetUtilService.postWidgetCreate(scope, element, attrs); } diff --git a/src/main/webapp/scripts/modules/widgets/basic/label/label.js b/src/main/webapp/scripts/modules/widgets/basic/label/label.js index df14f9b4c..2cdf8073b 100644 --- a/src/main/webapp/scripts/modules/widgets/basic/label/label.js +++ b/src/main/webapp/scripts/modules/widgets/basic/label/label.js @@ -5,7 +5,7 @@ WM.module('wm.widgets.basic') .run(['$templateCache', function ($templateCache) { 'use strict'; $templateCache.put('template/widget/label.html', - '' + '' ); }]) .directive('wmLabel', ['PropertiesFactory', 'WidgetUtilService', 'Utils', function (PropertiesFactory, WidgetUtilService, Utils) { @@ -13,11 +13,12 @@ WM.module('wm.widgets.basic') var widgetProps = PropertiesFactory.getPropertiesOf('wm.label', ['wm.base', 'wm.base.advancedformwidgets', 'wm.base.events']), notifyFor = { 'caption' : true, - 'required': true + 'required': true, + 'hint': true }; /* Define the property change handler. This function will be triggered when there is a change in the widget property */ - function propertyChangeHandler(element, key, newVal) { + function propertyChangeHandler(element, attrs, key, newVal) { switch (key) { case 'caption': Utils.setNodeContent(element, newVal); @@ -29,6 +30,9 @@ WM.module('wm.widgets.basic') element.removeClass('required'); } break; + case 'hint': + attrs.$set('title', newVal); + break; } } @@ -44,7 +48,7 @@ WM.module('wm.widgets.basic') 'post': function (scope, element, attrs) { /* register the property change handler */ - WidgetUtilService.registerPropertyChangeListener(propertyChangeHandler.bind(undefined, element), scope, notifyFor); + WidgetUtilService.registerPropertyChangeListener(propertyChangeHandler.bind(undefined, element, attrs), scope, notifyFor); WidgetUtilService.postWidgetCreate(scope, element, attrs); } } diff --git a/src/main/webapp/scripts/modules/widgets/basic/picture/picture.js b/src/main/webapp/scripts/modules/widgets/basic/picture/picture.js index 47a292c8b..00c6549e4 100644 --- a/src/main/webapp/scripts/modules/widgets/basic/picture/picture.js +++ b/src/main/webapp/scripts/modules/widgets/basic/picture/picture.js @@ -5,20 +5,25 @@ WM.module('wm.widgets.basic') .run(['$templateCache', function ($templateCache) { 'use strict'; $templateCache.put('template/widget/picture.html', - '{{hint}}' + '' ); + + $templateCache.put('template/mobile/widget/picture.html', + '' + ); }]) - .directive('wmPicture', ['PropertiesFactory', 'WidgetUtilService', 'Utils', function (PropertiesFactory, WidgetUtilService, Utils) { + .directive('wmPicture', ['PropertiesFactory', 'WidgetUtilService', 'Utils', 'CONSTANTS', function (PropertiesFactory, WidgetUtilService, Utils, CONSTANTS) { 'use strict'; var widgetProps = PropertiesFactory.getPropertiesOf('wm.picture', ['wm.base', 'wm.base.events']), notifyFor = { 'pictureaspect': true, 'picturesource': true, - 'shape': true + 'shape': true, + 'hint': true }; /* Define the property change handler. This function will be triggered when there is a change in the widget property */ - function propertyChangeHandler(scope, element, key, newVal) { + function propertyChangeHandler(scope, element, attrs, key, newVal) { switch (key) { case 'pictureaspect': switch (newVal) { @@ -34,6 +39,10 @@ WM.module('wm.widgets.basic') case 'Both': element.css({width: '100%', height: '100%'}); break; + case 'hint': + attrs.$set('title', newVal); + attrs.$set('alt', newVal); + break; } break; case 'picturesource': @@ -45,14 +54,19 @@ WM.module('wm.widgets.basic') scope.imgClass = "img-" + newVal; break; } - } return { 'restrict': 'E', 'replace' : true, 'scope' : {}, - 'template': WidgetUtilService.getPreparedTemplate.bind(undefined, 'template/widget/picture.html'), + 'template': function ($tEl, $tAttrs) { + var templateId = 'template/widget/picture.html'; + if (CONSTANTS.hasCordova) { + templateId = 'template/mobile/widget/picture.html'; + } + return WidgetUtilService.getPreparedTemplate(templateId, $tEl, $tAttrs); + }, 'link' : { 'pre': function (scope, $el, attrs) { scope.widgetProps = attrs.widgetid ? Utils.getClonedObject(widgetProps) : widgetProps; @@ -60,7 +74,7 @@ WM.module('wm.widgets.basic') 'post': function (scope, element, attrs) { /* register the property change handler */ - WidgetUtilService.registerPropertyChangeListener(propertyChangeHandler.bind(undefined, scope, element), scope, notifyFor); + WidgetUtilService.registerPropertyChangeListener(propertyChangeHandler.bind(undefined, scope, element, attrs), scope, notifyFor); WidgetUtilService.postWidgetCreate(scope, element, attrs); } } diff --git a/src/main/webapp/scripts/modules/widgets/basic/popover/popover.js b/src/main/webapp/scripts/modules/widgets/basic/popover/popover.js index 027d4d75e..267069194 100644 --- a/src/main/webapp/scripts/modules/widgets/basic/popover/popover.js +++ b/src/main/webapp/scripts/modules/widgets/basic/popover/popover.js @@ -3,162 +3,212 @@ /*Directive for popover */ WM.module('wm.widgets.basic') - .directive('wmPopover', ['PropertiesFactory', 'WidgetUtilService', 'Utils', 'CONSTANTS', '$rootScope', '$timeout', function (PropertiesFactory, WidgetUtilService, Utils, CONSTANTS, $rs, $timeout) { - 'use strict'; - - var widgetProps = PropertiesFactory.getPropertiesOf('wm.popover', ['wm.base', 'wm.base.advancedformwidgets', 'wm.anchor']), - notifyFor = { - 'iconclass' : true, - 'iconurl' : true, - 'caption' : true, - 'iconposition' : true, - 'contentsource' : true, - 'popoverplacement': CONSTANTS.isRunMode - }; - - function propertyChangeHandler($is, $el, transFn, key, nv) { - switch (key) { - case 'iconposition': - $el.attr('icon-position', nv); - break; - case 'contentsource': - //check for 2 option inline || partial - if (nv === 'inline') { - $is.widgetProps.inlinecontent.show = true; - $is.widgetProps.content.show = false; - } else { - $is.widgetProps.content.show = true; - $is.widgetProps.inlinecontent.show = false; - } + .directive('wmPopover', [ + 'PropertiesFactory', + 'WidgetUtilService', + 'Utils', + 'CONSTANTS', + '$rootScope', + '$timeout', + '$templateCache', + '$compile', + function (PropertiesFactory, WidgetUtilService, Utils, CONSTANTS, $rs, $timeout, $tc, $compile) { + 'use strict'; - transcludeContent($is, $el, transFn, nv); - break; - case 'iconclass': - /*showing icon when iconurl is not set*/ - $is.showicon = $is.iconclass !== '_none_' && nv !== '' && !$is.iconurl; - break; - case 'iconurl': - /*hiding icon when iconurl is set*/ - /*showing icon when iconurl is not set*/ - var showIcon = nv === ''; - $is.showicon = showIcon; - $is.showimage = !showIcon; - $is.iconsrc = Utils.getImageUrl(nv); - break; - case 'caption': - Utils.setNodeContent($el.find('>span.anchor-caption'), nv); - break; - case 'popoverplacement': - if (nv) { - $is._popoverOptions.placement = 'auto ' + nv; - } - break; - } - } + var widgetProps = PropertiesFactory.getPropertiesOf('wm.popover', ['wm.base', 'wm.base.advancedformwidgets', 'wm.anchor']), + notifyFor = { + 'iconclass' : true, + 'iconurl' : true, + 'caption' : true, + 'iconposition' : true, + 'contentsource' : true, + 'popoverplacement': CONSTANTS.isRunMode + }, + interaction = { + 'click' : {'outsideClick' : 'outsideClick'}, + 'hover' : {'mouseenter' : 'none'}, + 'default' : {'mouseenter' : 'none', 'outsideClick': 'outsideClick'} + }; - //Sets style block for popover to set height and width of popover - function setStyleBlock($is) { - //Add style block to set width and height of popover to avoid flickering effect - var styleBlock = document.head.getElementsByClassName('popover-styles'), - css = '.' + $is._popoverOptions.customclass + '{height: ' + Utils.formatStyle($is.popoverheight, 'px') + '; width: ' + Utils.formatStyle($is.popoverwidth, 'px') + '}'; + //Compile partial params by calling transclude fn + function transcludeContent($el, slotContent) { - if (!$is.popoverarrow) { - css += '.' + $is._popoverOptions.customclass + ' .arrow {display: none !important;}'; - } - if (!styleBlock.length) { - styleBlock = document.createElement('style'); - styleBlock.setAttribute('type', 'text/css'); - styleBlock.setAttribute('class', 'popover-styles'); - styleBlock.textContent = css; - document.head.appendChild(styleBlock); - } else { - styleBlock[0].sheet.insertRule(css, 0); + if (slotContent.children.length && slotContent.children[0].tagName === 'WM-PARAM') { + var $nodes = WM.element(slotContent.children); + $el.append($nodes); + $compile($nodes)($el.scope()); + } } - } - //Hides the popover - function setHideTrigger($is) { - $timeout(function () { - if (!$is._popoverOptions.isPopoverActive) { - $is._popoverOptions.isOpen = false; - } - }, 500, true); - } + function propertyChangeHandler($is, $el, slotContent, key, nv) { + var showIcon; + switch (key) { + case 'iconposition': + $el.attr('icon-position', nv); + break; + case 'contentsource': + //check for 2 option inline || partial + if ($is.widgetid) { + if (nv === 'inline') { + $is.widgetProps.inlinecontent.show = true; + $is.widgetProps.content.show = false; + } else { + $is.widgetProps.content.show = true; + $is.widgetProps.inlinecontent.show = false; + } + } - //Compile partial params by calling transclude fn - function transcludeContent($is, $el, transFn, nv) { + transcludeContent($el, slotContent); + break; + case 'iconclass': + /*showing icon when iconurl is not set*/ + $is.showicon = $is.iconclass !== '_none_' && nv !== '' && !$is.iconurl; + break; + case 'iconurl': + /*hiding icon when iconurl is set*/ + /*showing icon when iconurl is not set*/ + showIcon = nv === ''; + $is.showicon = showIcon; + $is.showimage = !showIcon; + $is.iconsrc = Utils.getImageUrl(nv); + break; + case 'caption': + Utils.setNodeContent($el.find('>span.anchor-caption'), nv); + break; + case 'popoverplacement': + if (nv) { + $is._popoverOptions.placement = 'auto ' + nv; + } + break; + } + } - transFn($el.scope(), function (clone) { + //Sets style block for popover to set height and width of popover + function setStyleBlock($is) { + //Add style block to set width and height of popover to avoid flickering effect + var styleBlock = document.head.getElementsByClassName('popover-styles'), + css = '.' + $is._popoverOptions.customclass + '{height: ' + Utils.formatStyle($is.popoverheight, 'px') + '; width: ' + Utils.formatStyle($is.popoverwidth, 'px') + '}', + arrowCss; - if (clone.length) { - //If contentsource is inline set inline content - if (nv === 'inline') { - $is.inlinecontent = $is._popoverOptions.content = WM.element(clone)[0].innerHTML; - } else if (clone[0].tagName === 'WM-PARAM') { - //If element is param append to $el - $el.append(clone); + if (!$is.popoverarrow) { + arrowCss = '.' + $is._popoverOptions.customclass + ' .arrow {display: none !important;}'; + } + if (!styleBlock.length) { + styleBlock = document.createElement('style'); + styleBlock.setAttribute('type', 'text/css'); + styleBlock.setAttribute('class', 'popover-styles'); + styleBlock.textContent = css + (arrowCss || ''); + document.head.appendChild(styleBlock); + } else { + styleBlock[0].sheet.insertRule(css, 0); + if (arrowCss) { + styleBlock[0].sheet.insertRule(arrowCss, 0); } } - }); - } + } + + //Hides the popover + function setHideTrigger($is) { + $timeout(function () { + if (!$is._popoverOptions.isPopoverActive) { + $is._popoverOptions.isOpen = false; + } + }, 500, true); + } - return { - 'restrict': 'E', - 'replace': true, - 'scope': {}, - 'transclude': true, - 'template': function (tElement, tAttrs) { - var template = WM.element(WidgetUtilService.getPreparedTemplate('template/widget/anchor.html', tElement, tAttrs)); - - //Required in studio mode also to set partial params which is based on pageContainer and wmtransclude directives - template.attr({ - 'page-container' : 'page-container' - }); - - if (CONSTANTS.isRunMode) { - //popover uses anchor template, so add below attributes on anchor markup to use uib-popover and also setting partial content + return { + 'restrict': 'E', + 'replace': true, + 'scope': {}, + 'template': function (tElement, tAttrs) { + var template = WM.element(WidgetUtilService.getPreparedTemplate('template/widget/anchor.html', tElement, tAttrs)); + + //Required in studio mode also to set partial params which is based on pageContainer and wmtransclude directives template.attr({ - 'uib-popover' : '{{_popoverOptions.content}}', - 'uib-popover-template' : '_popoverOptions.contenturl', - 'popover-class' : '{{_popoverOptions.customclass}}', - 'popover-placement' : '{{_popoverOptions.placement}}', - 'ng-mouseleave' : '_popoverOptions.setHideTrigger()', - 'popover-trigger' : '_popoverOptions.trigger', - 'popover-title' : '{{title}}', - 'popover-is-open' : '_popoverOptions.isOpen', - 'popover-append-to-body': 'true' + 'page-container' : 'page-container' }); - } - return template[0].outerHTML; - }, - 'compile': function (tElement) { - return { - 'pre': function ($is, $el, attrs) { - var trigger = {'mouseenter': 'none', 'outsideClick': 'outsideClick'}; - $is._popoverOptions = {'trigger': trigger, 'setHideTrigger': setHideTrigger.bind(undefined, $is)}; - $is.widgetProps = attrs.widgetid ? Utils.getClonedObject(widgetProps) : widgetProps; - $is.$lazyLoad = WM.noop; - }, - 'post': function ($is, $el, attrs, nullCtrl, transcludeFn) { - var isInlineContent = attrs.contentsource === 'inline'; - - if (CONSTANTS.isRunMode) { - $is._isFirstTime = true; - if (isInlineContent) { - $is._popoverOptions.customclass = 'popover_' + $is.$id + '_' + _.toLower($rs.activePageName); - setStyleBlock($is); - } else { - var popoverScope = $is.$$childHead, - $popoverEl; + + if (CONSTANTS.isRunMode) { + //popover uses anchor template, so add below attributes on anchor markup to use uib-popover and also setting partial content + template.attr({ + 'popover-class' : '{{"app-popover " + _popoverOptions.customclass}}', + 'popover-placement' : '{{_popoverOptions.placement}}', + 'popover-trigger' : '_popoverOptions.trigger', + 'popover-title' : '{{title}}', + 'popover-is-open' : '_popoverOptions.isOpen', + 'popover-append-to-body': 'true' + }); + + template.attr('uib-popover-template', '_popoverOptions.contenturl'); + + //If interaction is not click then attach ng-mouseleave event + if (_.includes(['default', 'hover'], tAttrs.interaction)) { + template.attr('ng-mouseleave', '_popoverOptions.setHideTrigger()'); + } + } + return template[0].outerHTML; + }, + 'compile': function (tElement) { + return { + 'pre': function ($is, $el, attrs) { + var trigger = attrs.interaction ? interaction[attrs.interaction] : interaction.click; + $is._popoverOptions = {'trigger': trigger, 'setHideTrigger': setHideTrigger.bind(undefined, $is)}; + $is.widgetProps = attrs.widgetid ? Utils.getClonedObject(widgetProps) : widgetProps; + $is.$lazyLoad = WM.noop; + $el.removeAttr('title'); + }, + 'post': function ($is, $el, attrs) { + var isInlineContent = attrs.contentsource === 'inline', + popoverScope, + $popoverEl, + _scope = $el.scope(); //scope inherited from controller's scope + + $is.appLocale = _scope.appLocale; + + if (CONSTANTS.isRunMode) { + $is._isFirstTime = true; + if (isInlineContent) { + /* This is to make the "Variables" & "Widgets" available in the inline content + * widgets it gets compiled with the popover isolate Scope + * and "Variables", "Widgets", "item" won't be available in that scope. */ + Object.defineProperties($is, { + 'Variables': { + 'get': function () { + return _scope.Variables; + } + }, + 'Widgets': { + 'get': function () { + return _scope.Widgets; + } + }, + 'item': { + 'get': function () { + return _scope.item; + } + } + }); + $is._popoverOptions.customclass = 'popover_' + $is.$id + '_' + _.toLower($rs.activePageName); + setStyleBlock($is); + + // create a template for the inline content + var popoverTemplate = 'template/popover/inline-content/' + $is.$id + '/content.html'; + $tc.put(popoverTemplate, '
    ' + tElement.context.innerHTML + '
    '); + $is._popoverOptions.contenturl = popoverTemplate; + } + + popoverScope = $is.$$childHead; + if (popoverScope) { /*Watch on popover isOpen to compile the partial markup - For first time when partial is not opened trigger the load to set partial content - */ + For first time when partial is not opened trigger the load to set partial content + */ popoverScope.$watch('isOpen', function (nv) { if (nv || $is._isFirstTime) { //Add custom mouseenter, leave events on popover $popoverEl = WM.element('.' + $is._popoverOptions.customclass); - if ($popoverEl.length) { + + if ($popoverEl.length && _.includes(['default', 'hover'], $is.interaction)) { $popoverEl.on('mouseenter', function () { $is._popoverOptions.isPopoverActive = true; $rs.$safeApply($is); @@ -168,24 +218,61 @@ WM.module('wm.widgets.basic') $is._popoverOptions.setHideTrigger(true); }); } + if ($is._popoverOptions.customclass) { setStyleBlock($is); } - Utils.triggerFn($is.$lazyLoad); + + if (!isInlineContent) { + Utils.triggerFn($is.$lazyLoad); + } + + $timeout(function () { + //On Open trigger onShow event + if (nv) { + if (!isInlineContent) { + + if ($popoverEl.length) { + var $parEl = $popoverEl.find('> .popover-inner > .popover-content > div > section.app-partial'), + partialScope; + + if ($parEl.length) { + partialScope = $parEl.scope(); + $is.Widgets = partialScope.Widgets; + $is.Variables = partialScope.Variables; + } + } + Utils.triggerFn($is.onLoad, {'$isolateScope': $is}); + } + + Utils.triggerFn($is.onShow, {'$isolateScope' : $is}); + } + }); + $is._isFirstTime = false; + } else { + Utils.triggerFn($is.onHide, {'$isolateScope' : $is}); } }); } } - } - WidgetUtilService.registerPropertyChangeListener(propertyChangeHandler.bind(undefined, $is, $el, transcludeFn), $is, notifyFor); - WidgetUtilService.postWidgetCreate($is, $el, attrs); - } - }; - } - }; - }]); + WidgetUtilService.registerPropertyChangeListener( + propertyChangeHandler.bind(undefined, $is, $el, tElement.context), + $is, + notifyFor + ); + WidgetUtilService.postWidgetCreate($is, $el, attrs); + + if ($is.widgetid && isInlineContent) { + $is.inlinecontent = tElement.context.innerHTML; + } + } + }; + } + }; + } + ]); /** * @ngdoc directive diff --git a/src/main/webapp/scripts/modules/widgets/basic/progressbar/progressbar.js b/src/main/webapp/scripts/modules/widgets/basic/progressbar/progressbar.js index afb0b7678..77ca402b4 100644 --- a/src/main/webapp/scripts/modules/widgets/basic/progressbar/progressbar.js +++ b/src/main/webapp/scripts/modules/widgets/basic/progressbar/progressbar.js @@ -95,6 +95,16 @@ WM.module('wm.widgets.basic') } } + // This function returns the maximum number of decimal digits allowed. + function getDecimalCount(val) { + val = val || '9'; + val = val.replace(/\%$/, ''); + + var n = val.lastIndexOf('.'); + + return (n === -1) ? 0 : (val.length - n - 1); + } + //Updates caption placement of each progress bar function updateCaptionPlacement(element, isMultipleBar, captionPlacement) { if (isMultipleBar) { @@ -107,7 +117,7 @@ WM.module('wm.widgets.basic') } // if the progressbar is NOT multibar, update the bar when maxvalue, minvalue or datavalue are changed. - function updateProgressBar(scope, progressBarEl, oldDatavalue, newDatavalue) { + function updateProgressBar(scope, attrs, progressBarEl, oldDatavalue, newDatavalue) { var isValueAPercentage, progressBarWidth, @@ -138,10 +148,6 @@ WM.module('wm.widgets.basic') if (WM.isDefined(scope.datavalue)) { displayValue = scope.datavalue * 100 / (scope.maxvalue - scope.minvalue); progressBarWidth = displayValue + '%'; - - if (scope.displayformat === DISPLAY_FORMAT.PERCENTAGE) { - displayValue = progressBarWidth; - } } else { displayValue = progressBarWidth = 0; } @@ -149,6 +155,17 @@ WM.module('wm.widgets.basic') progressBarEl.css('width', progressBarWidth); if (!$label.length || (newDatavalue !== oldDatavalue)) { + // support for percentage / absolute displayformat in old project. + if (attrs.displayformat === DISPLAY_FORMAT.PERCENTAGE) { + displayValue = progressBarWidth; + } else if (attrs.displayformat !== DISPLAY_FORMAT.ABSOLUTE) { + displayValue = (displayValue.toFixed(getDecimalCount(scope.displayformat))); + + if (_.includes(scope.displayformat, '%')) { + displayValue = displayValue + '%'; + } + } + if ($label.length) { $label.text(displayValue).attr('data-caption-placement', scope.captionplacement); } else { @@ -214,18 +231,18 @@ WM.module('wm.widgets.basic') }); } - function propertyChangeHandler(scope, element, progressBarEl, key, newVal, oldVal) { + function propertyChangeHandler(scope, element, attrs, progressBarEl, key, newVal, oldVal) { switch (key) { case 'minvalue': case 'maxvalue': - updateProgressBar(scope, progressBarEl); + updateProgressBar(scope, attrs, progressBarEl); break; case 'datavalue': if (!(WM.isArray(scope.dataset))) { scope.isMultipleBar = false; element.children('.multi-bar').remove(); if (WM.isNumber(newVal) || WM.isString(newVal)) { - updateProgressBar(scope, progressBarEl, oldVal, newVal); + updateProgressBar(scope, attrs, progressBarEl, oldVal, newVal); } } break; @@ -273,7 +290,7 @@ WM.module('wm.widgets.basic') } } - WidgetUtilService.registerPropertyChangeListener(propertyChangeHandler.bind(undefined, scope, element, progressBarEl), scope, notifyFor); + WidgetUtilService.registerPropertyChangeListener(propertyChangeHandler.bind(undefined, scope, element, attrs, progressBarEl), scope, notifyFor); WidgetUtilService.postWidgetCreate(scope, element, attrs); } } diff --git a/src/main/webapp/scripts/modules/widgets/basic/search/search.js b/src/main/webapp/scripts/modules/widgets/basic/search/search.js index 255aed497..7ee0359bc 100644 --- a/src/main/webapp/scripts/modules/widgets/basic/search/search.js +++ b/src/main/webapp/scripts/modules/widgets/basic/search/search.js @@ -14,7 +14,7 @@ WM.module('wm.widgets.basic') $templateCache.put('template/widget/form/search.html', '' + '

    ' ); diff --git a/src/main/webapp/scripts/modules/widgets/basic/spinner/spinnerService.js b/src/main/webapp/scripts/modules/widgets/basic/spinner/spinnerService.js index b5530ad7c..13da94fa2 100644 --- a/src/main/webapp/scripts/modules/widgets/basic/spinner/spinnerService.js +++ b/src/main/webapp/scripts/modules/widgets/basic/spinner/spinnerService.js @@ -17,7 +17,7 @@ WM.module('wm.widgets.basic'). function getAppSpinnerScope() { if (!appSpinner) { - appSpinner = WM.element('body >.app-spinner:first').isolateScope(); + appSpinner = WM.element('[name=globalspinner], [name=global-spinner]').eq(0).isolateScope(); } return appSpinner; } diff --git a/src/main/webapp/scripts/modules/widgets/basic/tree/tree.js b/src/main/webapp/scripts/modules/widgets/basic/tree/tree.js index 658ba8e22..e69d12cc5 100644 --- a/src/main/webapp/scripts/modules/widgets/basic/tree/tree.js +++ b/src/main/webapp/scripts/modules/widgets/basic/tree/tree.js @@ -27,7 +27,7 @@ WM.module('wm.widgets.basic') ICON_CLASSES = { 'folder': { 'expanded' : 'wi-folder-open', - 'collapsed': 'wi-folder-close' + 'collapsed': 'wi-folder' }, 'circle-plus-minus': { 'expanded' : 'wi-remove-circle-outline', @@ -53,13 +53,14 @@ WM.module('wm.widgets.basic') 'expanded' : 'wi-minus', 'collapsed': 'wi-plus' } - }; + }, + TypeUtils; function constructNodes($is, nodes, parent, levels, deep, _evalDataValue) { var $ul = WM.element('
      '), _iconClses = ICON_CLASSES[$is.treeicons || defaultTreeIconClass], - _expr = CONSTANTS.isRunMode ? $is.datavalue : undefined, + _expr = CONSTANTS.isRunMode ? ($is.binddatavalue ? $is.binddatavalue.replace('bind:', '') : $is.datavalue) : undefined, _iconCls, _cls; @@ -171,6 +172,10 @@ WM.module('wm.widgets.basic') $li.removeClass('expanded').addClass('collapsed'); } } + //returns the datavalue type from the nodeid + function getDataValueType($is) { + return TypeUtils.getTypeForExpression($is.binddataset, $is, 'nodeid'); + } function renderTree($el, $is, attrs) { var levels = +attrs.levels || 0, @@ -252,8 +257,10 @@ WM.module('wm.widgets.basic') path = '', $liPath, fn; - $el.find('.selected').removeClass('selected'); + if (!$li.length) { + return; + } $li.addClass('selected'); data = $li.data('nodedata'); @@ -280,7 +287,13 @@ WM.module('wm.widgets.basic') //if it is a click event update the datavalue and assign a watch as the previous watch will break after assigning if (target) { - $is.datavalue = WidgetUtilService.getEvaluatedData($is, data, {expressionName: 'nodeid'}); + if ($is.nodeid) { + $is.datavalue = $is.$eval($is.nodeid, data); + } else if ($is.bindnodeid) { + $is.datavalue = WidgetUtilService.getEvaluatedData($is, data, {expressionName: 'nodeid'}); + } else { + $is.datavalue = Utils.getClonedObject(data) || {}; + } $is.$watch('datavalue', function (newVal) { propertyChangeHandler($is, undefined, undefined, 'datavalue', newVal); }, true); @@ -325,6 +338,9 @@ WM.module('wm.widgets.basic') if (!$is.widgetid) { bindEvents($is, $el); + } else { + TypeUtils = Utils.getService('TypeUtils'); + $is.getDataValueType = getDataValueType.bind(undefined, $is); } // wait till all the properties are set in the scope. @@ -332,6 +348,11 @@ WM.module('wm.widgets.basic') $is.selectNodeById = selectNode.bind(undefined, $is, $el, WM.noop()); + $is.deselectNode = function () { + $is.selecteddata = {}; + $is.selectNodeById(); + }; + var onPropertyChange = propertyChangeHandler.bind(undefined, $is, $el, attrs); WidgetUtilService.registerPropertyChangeListener(onPropertyChange, $is, notifyFor); @@ -345,6 +366,9 @@ WM.module('wm.widgets.basic') if (!attrs.widgetid && attrs.datavalue) { $is.$watch('datavalue', function (newVal) { + if (!newVal) { + return; + } onPropertyChange('datavalue', newVal); }, true); } diff --git a/src/main/webapp/scripts/modules/widgets/dialog/alertdialog/alertdialog.js b/src/main/webapp/scripts/modules/widgets/dialog/alertdialog/alertdialog.js index d451e58e7..30491aad9 100644 --- a/src/main/webapp/scripts/modules/widgets/dialog/alertdialog/alertdialog.js +++ b/src/main/webapp/scripts/modules/widgets/dialog/alertdialog/alertdialog.js @@ -7,7 +7,7 @@ WM.module('wm.widgets.dialog') $templateCache.put("template/widget/dialog/alertdialog.html", '' ); - }]).directive('wmAlertdialog', ["$templateCache", "PropertiesFactory", "WidgetUtilService", "CONSTANTS", 'Utils', '$window', function ($templateCache, PropertiesFactory, WidgetUtilService, CONSTANTS, Utils, $window) { + }]).directive('wmAlertdialog', ["$templateCache", "PropertiesFactory", "WidgetUtilService", "CONSTANTS", 'Utils', '$window', 'DeviceService', function ($templateCache, PropertiesFactory, WidgetUtilService, CONSTANTS, Utils, $window, DeviceService) { 'use strict'; var widgetProps = PropertiesFactory.getPropertiesOf("wm.alertdialog", ["wm.basicdialog", "wm.base", "wm.dialog.onOk"]), notifyFor = { @@ -100,7 +100,17 @@ WM.module('wm.widgets.dialog') } }, "post": function (scope, element, attrs, dialogCtrl) { - var modalWindowElScope = element.closest('[uib-modal-window]').isolateScope(); + var modalWindowElScope = element.closest('[uib-modal-window]').isolateScope(), + backButtonListenerDeregister; + if (CONSTANTS.isRunMode && element.attr('inscript')) { + backButtonListenerDeregister = DeviceService.onBackButtonTap(function () { + dialogCtrl._CancelButtonHandler(); + return false; + }); + scope.$on('$destroy', function () { + backButtonListenerDeregister(); + }); + } /* handles ok button click*/ if (!scope.okButtonHandler) { scope.okButtonHandler = function () { @@ -163,6 +173,8 @@ WM.module('wm.widgets.dialog') * show is a bindable property.
      * This property will be used to show/hide the dialog on the web page.
      * Default value: `true`. + * @param {boolean=} closable + * closable enables close icon on header also enables close of dialog with ESC key * @param {list=} animation * This property controls the animation of the dialog.
      * The animation is based on the css classes and works only in the run mode.
      diff --git a/src/main/webapp/scripts/modules/widgets/dialog/confirmdialog/confirmdialog.js b/src/main/webapp/scripts/modules/widgets/dialog/confirmdialog/confirmdialog.js index 73eba42db..41bfb8fa2 100644 --- a/src/main/webapp/scripts/modules/widgets/dialog/confirmdialog/confirmdialog.js +++ b/src/main/webapp/scripts/modules/widgets/dialog/confirmdialog/confirmdialog.js @@ -7,7 +7,7 @@ WM.module('wm.widgets.dialog') $templateCache.put("template/widget/dialog/confirmdialog.html", '' ); - }]).directive('wmConfirmdialog', ["$templateCache", "PropertiesFactory", "WidgetUtilService", "CONSTANTS", 'Utils', '$window', function ($templateCache, PropertiesFactory, WidgetUtilService, CONSTANTS, Utils, $window) { + }]).directive('wmConfirmdialog', ["$templateCache", "PropertiesFactory", "WidgetUtilService", "CONSTANTS", 'Utils', '$window', 'DeviceService', function ($templateCache, PropertiesFactory, WidgetUtilService, CONSTANTS, Utils, $window, DeviceService) { 'use strict'; var widgetProps = PropertiesFactory.getPropertiesOf("wm.confirmdialog", ["wm.basicdialog", "wm.base", "wm.dialog.onOk"]), notifyFor = { @@ -99,7 +99,8 @@ WM.module('wm.widgets.dialog') } }, "post": function (scope, element, attrs, dialogCtrl) { - var modalWindowElScope = element.closest('[uib-modal-window]').isolateScope(); + var modalWindowElScope = element.closest('[uib-modal-window]').isolateScope(), + backButtonListenerDeregister; /* handles cancel button click*/ if (!scope.cancelButtonHandler) { @@ -113,6 +114,16 @@ WM.module('wm.widgets.dialog') dialogCtrl._OkButtonHandler(attrs.onOk); }; } + + if (CONSTANTS.isRunMode && element.attr('inscript')) { + backButtonListenerDeregister = DeviceService.onBackButtonTap(function () { + scope.cancelButtonHandler(); + return false; + }); + scope.$on('$destroy', function () { + backButtonListenerDeregister(); + }); + } /*adding classes for ok and cancel button for studio*/ if (scope.okbuttonclass) { WM.element(element.find('button')[1]).addClass(scope.okbuttonclass); @@ -175,6 +186,8 @@ WM.module('wm.widgets.dialog') * show is a bindable property.
      * This property will be used to show/hide the accordion on the web page.
      * Default value: `true`. + * @param {boolean=} closable + * closable enables close icon on header also enables close of dialog with ESC key * @param {list=} animation * This property controls the animation of the dialog.
      * The animation is based on the css classes and works only in the run mode.
      @@ -200,7 +213,7 @@ WM.module('wm.widgets.dialog')
      - + diff --git a/src/main/webapp/scripts/modules/widgets/dialog/controllers/dialogcontroller.js b/src/main/webapp/scripts/modules/widgets/dialog/controllers/dialogcontroller.js index d31466112..4457427c6 100644 --- a/src/main/webapp/scripts/modules/widgets/dialog/controllers/dialogcontroller.js +++ b/src/main/webapp/scripts/modules/widgets/dialog/controllers/dialogcontroller.js @@ -14,11 +14,8 @@ WM.module('wm.widgets.dialog') * else invoke the service and finally close the current dialog*/ if (eventName && eventName.indexOf("(") !== -1) { Utils.triggerFn(callBack, callbackParams); - return; - } - - // Studio Dialogs without individual templates do not have a "(" in the eventName. callBack() will return a reference to the actual callback. - if (callBack) { + } else if (callBack) { + // Studio Dialogs without individual templates do not have a "(" in the eventName. callBack() will return a reference to the actual callback. if (CONSTANTS.isStudioMode) { if (WM.isFunction(callBack())) { Utils.triggerFn(callBack(), callbackParams); diff --git a/src/main/webapp/scripts/modules/widgets/dialog/dialog.js b/src/main/webapp/scripts/modules/widgets/dialog/dialog.js index 51cecd61f..90784f28b 100644 --- a/src/main/webapp/scripts/modules/widgets/dialog/dialog.js +++ b/src/main/webapp/scripts/modules/widgets/dialog/dialog.js @@ -153,7 +153,7 @@ WM.module('wm.widgets.dialog') }; } }; - }]).directive('wmDialogContainer', ["$templateCache", "PropertiesFactory", "WidgetUtilService", "CONSTANTS", '$window', 'Utils', 'DialogService', function ($templateCache, PropertiesFactory, WidgetUtilService, CONSTANTS, $window, Utils, DialogService) { + }]).directive('wmDialogContainer', ["$templateCache", "PropertiesFactory", "WidgetUtilService", "CONSTANTS", '$window', 'Utils', 'DialogService', 'DeviceService', function ($templateCache, PropertiesFactory, WidgetUtilService, CONSTANTS, $window, Utils, DialogService, DeviceService) { 'use strict'; var widgetProps = PropertiesFactory.getPropertiesOf("wm.designdialog", ["wm.basicdialog", "wm.base"]), notifyFor = { @@ -216,11 +216,8 @@ WM.module('wm.widgets.dialog') * else invoke the service and finally close the current dialog*/ if (eventName && eventName.indexOf("(") !== -1) { Utils.triggerFn(callBack, callbackParams); - return; - } - - // Studio Dialogs without individual templates do not have a "(" in the eventName. callBack() will return a reference to the actual callback. - if (callBack) { + } else if (callBack) { + // Studio Dialogs without individual templates do not have a "(" in the eventName. callBack() will return a reference to the actual callback. if (CONSTANTS.isStudioMode) { if (WM.isFunction(callBack())) { Utils.triggerFn(callBack(), callbackParams); @@ -291,6 +288,7 @@ WM.module('wm.widgets.dialog') "compile": function () { return { "pre": function (scope, element, attrs, ctrl) { + var backButtonListenerDeregister; scope.__readyQueue = []; scope.widgetProps = attrs.widgetid ? Utils.getClonedObject(widgetProps) : widgetProps; scope.whenReady = function (fn) { @@ -301,6 +299,13 @@ WM.module('wm.widgets.dialog') }; scope.open = Utils.openDialog.bind(undefined, scope.dialogid); scope.close = DialogService.close.bind(undefined, scope.dialogid); + backButtonListenerDeregister = DeviceService.onBackButtonTap(function () { + scope.hideDialog(); + return false; + }); + scope.$on('$destroy', function () { + backButtonListenerDeregister(); + }); }, "post": function (scope, element, attrs, ctrl) { var modalWindowElScope = element.closest('[uib-modal-window]').isolateScope(); @@ -477,6 +482,8 @@ WM.module('wm.widgets.dialog') * @param {boolean=} modal * True value for Modal property shows up a modal dialog.
      * Default value:`true`. + * @param {boolean=} closable + * closable enables close icon on header also enables close of dialog with ESC key * @param {string=} iconclass * iconclass sets the icon for dialog header * @param {string=} on-close @@ -499,7 +506,7 @@ WM.module('wm.widgets.dialog')
      - +
      @@ -510,6 +517,9 @@ WM.module('wm.widgets.dialog') $scope.hideDialog = function () { DialogService.close('sampleDialog'); } + $scope.showDialog = function () { + DialogService.open("sampleDialog"); + } }
      @@ -545,6 +555,8 @@ WM.module('wm.widgets.dialog') * Possible values are "bounce", "flash", "pulse", "rubberBand", "shake", etc. * @param {string=} iconclass * iconclass sets the icon for dialog header + * @param {boolean=} closable + * closable enables close icon on header also enables close of dialog with ESC key * @param {string=} iconwidth * Optional, This sets the width of the icon in dialog header. * @param {string=} iconheight diff --git a/src/main/webapp/scripts/modules/widgets/dialog/iframedialog/iframedialog.js b/src/main/webapp/scripts/modules/widgets/dialog/iframedialog/iframedialog.js index e41e40085..2abb81f67 100644 --- a/src/main/webapp/scripts/modules/widgets/dialog/iframedialog/iframedialog.js +++ b/src/main/webapp/scripts/modules/widgets/dialog/iframedialog/iframedialog.js @@ -132,6 +132,8 @@ WM.module('wm.widgets.dialog') * show is a bindable property.
      * This property will be used to show/hide the dialog on the web page.
      * Default value: `true`. + * @param {boolean=} closable + * closable enables close icon on header also enables close of dialog with ESC key * @param {string=} url * url sets the url whose content needs to be shown in the iframe dialog * @param {list=} animation diff --git a/src/main/webapp/scripts/modules/widgets/dialog/logindialog/logindialog.js b/src/main/webapp/scripts/modules/widgets/dialog/logindialog/logindialog.js index cb84d527f..5eaee6cfa 100644 --- a/src/main/webapp/scripts/modules/widgets/dialog/logindialog/logindialog.js +++ b/src/main/webapp/scripts/modules/widgets/dialog/logindialog/logindialog.js @@ -7,7 +7,7 @@ WM.module('wm.widgets.dialog') $templateCache.put('template/widget/dialog/logindialog.html', '' @@ -245,6 +245,8 @@ WM.module('wm.widgets.dialog') * show is a bindable property.
      * This property will be used to show/hide the dialog on the web page.
      * Default value: `true`. + * @param {boolean=} closable + * closable enables close icon on header also enables close of dialog with ESC key * @param {string=} iconclass * Icon class for the icon in dialog header * diff --git a/src/main/webapp/scripts/modules/widgets/dialog/pagedialog/pagedialog.js b/src/main/webapp/scripts/modules/widgets/dialog/pagedialog/pagedialog.js index 8504e5248..dd143576f 100644 --- a/src/main/webapp/scripts/modules/widgets/dialog/pagedialog/pagedialog.js +++ b/src/main/webapp/scripts/modules/widgets/dialog/pagedialog/pagedialog.js @@ -171,6 +171,8 @@ WM.module('wm.widgets.dialog') * show is a bindable property.
      * This property will be used to show/hide the dialog on the web page.
      * Default value:`true`. + * @param {boolean=} closable + * closable enables close icon on header also enables close of dialog with ESC key * @param {string=} page * page sets the page from the project whose content needs to be shown in the page dialog. * @param {list=} content @@ -205,7 +207,7 @@ WM.module('wm.widgets.dialog') oktext="OK" on-ok="onOkCallBack()" on-close="onCloseCallBack()"> - +

      @@ -221,6 +223,9 @@ WM.module('wm.widgets.dialog') $scope.onOpenedCallBack = function () { console.log("inside opened callback"); } + $scope.showDialog = function () { + DialogService.open("pageDialog"); + } } diff --git a/src/main/webapp/scripts/modules/widgets/dialog/services/dialogservice.js b/src/main/webapp/scripts/modules/widgets/dialog/services/dialogservice.js index cf97c641d..fe6df7cc1 100644 --- a/src/main/webapp/scripts/modules/widgets/dialog/services/dialogservice.js +++ b/src/main/webapp/scripts/modules/widgets/dialog/services/dialogservice.js @@ -75,7 +75,7 @@ WM.module('wm.widgets.dialog') } else { backdrop = true; } - keyboard = template.attr('keyboard'); + keyboard = template.attr('closable'); /* to change original keyboard value(which is string) to boolean because $uibModal.open expects boolean */ keyboard = keyboard !== 'false'; /* in case no params are passed, creating an empty object*/ @@ -245,7 +245,7 @@ WM.module('wm.widgets.dialog') backdrop = true; } } - keyboard = params.keyboard || template.attr('keyboard'); + keyboard = params.closable || template.attr('closable'); if (keyboard === 'false') { /* to change original keyboard value(which is string) to boolean because $uibModal.open expects boolean */ keyboard = false; diff --git a/src/main/webapp/scripts/modules/widgets/form/button/button.js b/src/main/webapp/scripts/modules/widgets/form/button/button.js index 360b1cb4d..ef73dc45c 100644 --- a/src/main/webapp/scripts/modules/widgets/form/button/button.js +++ b/src/main/webapp/scripts/modules/widgets/form/button/button.js @@ -6,8 +6,8 @@ WM.module('wm.widgets.form') 'use strict'; $templateCache.put('template/widget/form/button.html', - '' + /* list of selectedfiles UI */ '
        ' + - '
      • ' + + '
      • ' + '
        ' + '
        ' + '
        ' + @@ -55,7 +55,7 @@ WM.module('wm.widgets.form') '
      ' + /* list of uploadedfiles UI */ '
      ' @@ -32,7 +32,7 @@ WM.module('wm.widgets.form') ); $templateCache.put('template/widget/form/menu/dropdownItem.html', '
    • ' + - '' + + '' + '' + '' + '{{item.label}}' + @@ -74,18 +74,25 @@ WM.module('wm.widgets.form') } else if (WM.isArray(newVal)) { newVal = FormWidgetUtils.getOrderedDataSet(newVal, scope.orderby); if (WM.isObject(newVal[0])) { - transformFn = function (item) { + transformFn = function (result, item) { var children = (WidgetUtilService.getEvaluatedData(scope, item, {expressionName: 'itemchildren'}) || item.children); - return { - 'label' : WidgetUtilService.getEvaluatedData(scope, item, {expressionName: 'itemlabel'}) || item.label, - 'icon' : WidgetUtilService.getEvaluatedData(scope, item, {expressionName: 'itemicon'}) || item.icon, - 'disabled' : item.disabled, - 'link' : WidgetUtilService.getEvaluatedData(scope, item, {expressionName: 'itemlink'}) || item.link, - 'value' : scope.datafield ? (scope.datafield === 'All Fields' ? item : Utils.findValueOf(item, scope.datafield)) : item, - 'children' : (WM.isArray(children) ? children : []).map(transformFn) - }; + + if (Utils.validateAccessRoles(item[scope.userrole])) { + result.push({ + 'label' : WidgetUtilService.getEvaluatedData(scope, item, {expressionName: 'itemlabel'}) || item.label, + 'icon' : WidgetUtilService.getEvaluatedData(scope, item, {expressionName: 'itemicon'}) || item.icon, + 'disabled' : item.disabled, + 'link' : WidgetUtilService.getEvaluatedData(scope, item, {expressionName: 'itemlink'}) || item.link, + 'value' : scope.datafield ? (scope.datafield === 'All Fields' ? item : Utils.findValueOf(item, scope.datafield)) : item, + 'children' : (WM.isArray(children) ? children : []).reduce(transformFn, []), + 'action' : WidgetUtilService.getEvaluatedData(scope, item, {expressionName: 'itemaction'}) || item.action, + 'role' : WidgetUtilService.getEvaluatedData(scope, item, {expressionName: 'userrole'}) + }); + } + + return result; }; - menuItems = newVal.map(transformFn); + menuItems = newVal.reduce(transformFn, []); } else { menuItems = newVal.map(function (item) { return { @@ -124,10 +131,12 @@ WM.module('wm.widgets.form') case POSITION.UP_LEFT: element.addClass('dropup'); scope.menualign = 'pull-right'; + scope.menuCaret = "fa-caret-up"; break; case POSITION.UP_RIGHT: element.addClass('dropup'); scope.menualign = 'pull-left'; + scope.menuCaret = "fa-caret-up"; break; case POSITION.INLINE: scope.menualign = 'dropinline-menu'; @@ -157,6 +166,7 @@ WM.module('wm.widgets.form') 'compile': function (tElement) { return { 'pre': function (iScope, element, attrs) { + iScope.menuCaret = "fa-caret-down"; //@Deprecated iconname; use iconclass instead if (!attrs.iconclass && attrs.iconname) { WM.element(tElement.context).attr('iconclass', 'wi wi-' + attrs.iconname); @@ -241,9 +251,7 @@ WM.module('wm.widgets.form') 'template': $templateCache.get('template/widget/form/menu/dropdown.html'), 'link': function (scope, element) { scope.onSelect = function (args) { - if (!args.$scope.item.link) { - scope.$parent.onSelect(args); - } + scope.$parent.onSelect(args); }; if (CONSTANTS.isRunMode) { animation = element.parent().isolateScope().animateitems; @@ -258,8 +266,15 @@ WM.module('wm.widgets.form') } }; }]) - .directive('wmMenuDropdownItem', ['$templateCache', '$compile', 'CONSTANTS', function ($templateCache, $compile, CONSTANTS) { + .directive('wmMenuDropdownItem', ['$templateCache', '$compile', 'CONSTANTS', 'Utils', '$window', '$routeParams', function ($templateCache, $compile, CONSTANTS, Utils, $window, $routeParams) { 'use strict'; + function openLink(link, target) { + if (CONSTANTS.hasCordova && _.startsWith(link, '#')) { + location.href = link; + } else { + $window.open(link, target); + } + } return { 'restrict': "E", 'replace': true, @@ -276,14 +291,45 @@ WM.module('wm.widgets.form') return template[0].outerHTML; }, 'link': function (scope, element) { + var menuScope = element.closest('.dropdown').isolateScope(), + menuLink = scope.item[menuScope.itemlink], + routeRegex; + if (scope.item.children && scope.item.children.length > 0) { element.append(''); element.off('click'); $compile(element.contents())(scope); } + + //If nav item is menu then set it links active if route param is same as link + if (element.closest('.app-nav-item').length && menuLink) { + //itemLink can be #/routeName or #routeName + routeRegex = new RegExp('^(#\/|#)' + $routeParams.name + '$'); + if (routeRegex.test(menuLink)) { + element.addClass('active'); + } + } + scope.onSelect = function (args) { - if (!args.$scope.item.link) { - scope.$parent.onSelect(args); + var itemLink = WM.isObject(args.$item) ? args.$item[menuScope.itemlink || 'link'] : undefined, + itemAction = args.$item[menuScope.itemaction || 'action'], + linkTarget = menuScope.linktarget || '_self'; + + //If link starts with # and not with #/ replace with #/ + if (itemLink && _.startsWith(itemLink, '#') && !_.startsWith(itemLink, '#/')) { + itemLink = _.replace(itemLink, '#', '#/'); + } + + scope.$parent.onSelect(args); + if (itemAction) { + Utils.evalExp(element.closest('.dropdown').scope(), itemAction).then(function () { + if (itemLink) { + openLink(itemLink, linkTarget); + } + }); + } else if (itemLink) { + //If action is not present and link is there + openLink(itemLink, linkTarget); } }; } @@ -320,6 +366,16 @@ WM.module('wm.widgets.form') * Height of the menu. * @param {string=} scopedatavalue * This property accepts the value for the Menu widget from a variable defined in the script workspace.
      + * @param {string=} itemicon + * This property defines the value to be used as key for the icon from the list of values bound to the menu widget as an array of objects of different values. + * @param {string=} itemlabel + * This property defines the value to be used as key for the label from the list of values bound to the menu widget as an array of objects of different values. + * @param {string=} itemaction + * This property defines the value to be used as key for the action from the list of values bound to the menu widget as an array of objects of different values. + * @param {string=} itemlink + * This property defines the value to be used as key for the link from the list of values bound to the menu widget as an array of objects of different values. + * @param {string=} itemchildren + * This property specifies the sub-menu items * @param {string=} dataset * This property accepts the options to create the Menu widget from a wavemaker studio variable (live or static) which can hold object, array or string data. * @param {string=} datafield @@ -374,15 +430,18 @@ WM.module('wm.widgets.form') }, { "label": "item2", - "icon": "wi wi-euro-symbol" + "icon": "wi wi-euro-symbol", + "action": "Widgets.empForm.save()" }, { "label": "item3", - "icon": "wi wi-euro-symbol" + "icon": "wi wi-euro-symbol", + "action": "Widgets.empForm.new()" }, { "label": "item4", - "icon": "wi wi-euro-symbol" + "icon": "wi wi-euro-symbol", + "action": "Widgets.empForm.reset()" } ]; } diff --git a/src/main/webapp/scripts/modules/widgets/form/radioset/radioset.js b/src/main/webapp/scripts/modules/widgets/form/radioset/radioset.js index 989e6972d..abd94aa44 100644 --- a/src/main/webapp/scripts/modules/widgets/form/radioset/radioset.js +++ b/src/main/webapp/scripts/modules/widgets/form/radioset/radioset.js @@ -10,7 +10,7 @@ WM.module('wm.widgets.form') '' ); }]) - .directive('wmRadioset', ['PropertiesFactory', 'WidgetUtilService', '$compile', 'CONSTANTS', 'Utils', 'FormWidgetUtils', '$templateCache', function (PropertiesFactory, WidgetUtilService, $compile, CONSTANTS, Utils, FormWidgetUtils, $templateCache) { + .directive('wmRadioset', ['PropertiesFactory', 'WidgetUtilService', '$compile', 'CONSTANTS', 'Utils', 'FormWidgetUtils', '$templateCache', 'LiveWidgetUtils', function (PropertiesFactory, WidgetUtilService, $compile, CONSTANTS, Utils, FormWidgetUtils, $templateCache, LiveWidgetUtils) { 'use strict'; /*getting widget properties for the specific widget*/ var widgetProps = PropertiesFactory.getPropertiesOf('wm.radioset', ['wm.base', 'wm.base.editors.dataseteditors']), @@ -175,6 +175,13 @@ WM.module('wm.widgets.form') constructRadioSet(scope, element, scope.scopedataset); }); } + //In run mode, If widget is bound to selecteditem subset, fetch the data dynamically + if (!attrs.widgetid && _.includes(scope.binddataset, 'selecteditem.')) { + LiveWidgetUtils.fetchDynamicData(scope, element.scope(), function (data) { + constructRadioSet(scope, element, data); + }); + } + element.removeAttr('tabindex'); } } diff --git a/src/main/webapp/scripts/modules/widgets/form/richtexteditor/richtexteditor.js b/src/main/webapp/scripts/modules/widgets/form/richtexteditor/richtexteditor.js index c14ed8e5f..745b4021f 100644 --- a/src/main/webapp/scripts/modules/widgets/form/richtexteditor/richtexteditor.js +++ b/src/main/webapp/scripts/modules/widgets/form/richtexteditor/richtexteditor.js @@ -7,10 +7,11 @@ WM.module('wm.widgets.form') $tc.put('template/widget/richtexteditor.html', '
      ' + '
      ' + - '
      ' + + '
      ' + '' + '
      ' ); + $tc.put('template/widget/richtexteditor/table.html', '
      '); }]) .directive('wmRichtexteditor', [ 'PropertiesFactory', @@ -22,6 +23,369 @@ WM.module('wm.widgets.form') function (PropertiesFactory, $tc, WidgetUtilService, $injector, Utils) { 'use strict'; + var toolbarConfig = {}, + isConfigLoaded = false; + + //creates the table markup + function createTableMarkup(tableParams) { + if (WM.isNumber(tableParams.rows) && WM.isNumber(tableParams.columns) + && tableParams.rows > 0 && tableParams.columns > 0) { + var table = "", + idxRow, + idxCol, + row; + for (idxRow = 0; idxRow < tableParams.rows; idxRow++) { + row = ""; + for (idxCol = 0; idxCol < tableParams.columns; idxCol++) { + row += ""; + } + table += row + ""; + } + return table + "
       
      "; + } + } + + //inserts the content into the rich text editor + function insertIntoTextEditor(scope, content) { + scope.$editor().wrapSelection('insertHtml', content); + } + + //sets the toolbar config to include new plugins + function setToolBarConfig(taOptions) { + toolbarConfig.insertTable = { + 'display': "
      " + + "
      ", + 'iconclass': 'fa fa-table', + 'tooltiptext': 'Insert table', + 'action': function (promise, restoreSelection) { + var backgroundPicker = _.get(this.$parent.tools, 'backgroundColor'), + fontColorPicker = _.get(this.$parent.tools, 'fontColor'), + watcher; + //close other colorpickers + if (_.get(backgroundPicker, 'isColorPickerOpen')) { + backgroundPicker.isOpen = false; + backgroundPicker.$element.find('.colorpicker').removeClass('colorpicker-visible'); + } + if (_.get(fontColorPicker, 'isColorPickerOpen')) { + fontColorPicker.isOpen = false; + fontColorPicker.$element.find('.colorpicker').removeClass('colorpicker-visible'); + } + //resume the editor selection after the popover closes. + watcher = this.$watch('popoverIsOpen', function (nv) { + if (!nv) { + watcher(); + restoreSelection(); + } + }); + return false; + }, + 'onSelect': function (result) { + this.$parent.popoverIsOpen = false; + insertIntoTextEditor(this, createTableMarkup(result)); + this.$root.$safeApply(this.$parent); + } + }; + toolbarConfig.fontColor = { + 'display': "
      " + + "
      ", + 'action': function (promise, restoreSelection) { + var backgroundPicker = _.get(this.$parent.tools, 'backgroundColor'), + tablePopover = _.get(this.$parent.tools, 'insertTable'); + if (_.get(backgroundPicker, 'isColorPickerOpen')) { + backgroundPicker.isOpen = false; + backgroundPicker.$element.find('.colorpicker').removeClass('colorpicker-visible'); + } + if (_.get(tablePopover, 'popoverIsOpen')) { + tablePopover.popoverIsOpen = false; + } + if (this.isOpen) { + this.$element.find('.colorpicker').removeClass('colorpicker-visible'); + } + this.isOpen = !this.isOpen; + if (!this.colorPicker || !this.colorPicker.foreColor) { + promise.resolve(); + return promise; + } + return this.$editor().wrapSelection('foreColor', this.colorPicker.foreColor); + } + }; + toolbarConfig.backgroundColor = { + 'display': "
      " + + "" + + "
      ", + 'action': function(promise, restoreSelection) { + var fontColorPicker = _.get(this.$parent.tools, 'fontColor'), + tablePopover = _.get(this.$parent.tools, 'insertTable'); + if (_.get(fontColorPicker, 'isColorPickerOpen')) { + fontColorPicker.isOpen = false; + fontColorPicker.$element.find('.colorpicker').removeClass('colorpicker-visible'); + } + if (_.get(tablePopover, 'popoverIsOpen')) { + tablePopover.popoverIsOpen = false; + } + if (this.isOpen) { + this.$element.find('.colorpicker').removeClass('colorpicker-visible'); + } + this.isOpen = !this.isOpen; + if (!this.colorPicker || !this.colorPicker.backColor) { + promise.resolve(); + return promise; + } + return this.$editor().wrapSelection('backColor', this.colorPicker.backColor); + } + }; + toolbarConfig.fontName = { + 'display': "
      " + + "" + + "
      ", + 'action': function (promise, restoreSelection) { + var font = this.font, + editor = this.$editor(), + $richTextEditor = editor.$parent.$element; + if (!font) { + return promise.resolve(); + } + this.font = undefined; + $richTextEditor.find('[contenteditable]').trigger('click'); + return this.$editor().wrapSelection('fontName', font); + }, + 'selectFont': function(event, font) { + this.$parent.font = font; + }, + 'options': [ + { name: 'Sans-Serif', css: 'Arial, Helvetica, sans-serif' }, + { name: 'Serif', css: "'times new roman', serif" },/* TODO: + { name: 'Wide', css: "'arial black', sans-serif" }, + { name: 'Narrow', css: "'arial narrow', sans-serif" }, + { name: 'Comic Sans MS', css: "'comic sans ms', sans-serif" },*/ + { name: 'Courier New', css: "'courier new', monospace" }, + { name: 'Garamond', css: 'garamond, serif' }, + { name: 'Georgia', css: 'georgia, serif' }, + { name: 'Tahoma', css: 'tahoma, sans-serif' },/* TODO: + { name: 'Trebuchet MS', css: "'trebuchet ms', sans-serif" }, + { name: "Helvetica", css: "'Helvetica Neue', Helvetica, Arial, sans-serif" }, + { name: 'Verdana', css: 'verdana, sans-serif' },*/ + { name: 'Proxima Nova', css: 'proxima_nova_rgregular' } + ] + }; + toolbarConfig.fontSize = { + 'display': "
      " + + "" + + "
      " + + "
      ", + 'action': function (promise, restoreSelection) { + var size = this.size, + editor = this.$editor(), + $richTextEditor = editor.$parent.$element; + if (!size) { + return promise.resolve(); + } + this.size = undefined; + $richTextEditor.find('[contenteditable]').trigger('click'); + return this.$editor().wrapSelection('fontSize', parseInt(size)); + }, + 'selectSize': function (event, size) { + this.$parent.size = size; + }, + 'options': [ + { name: 'xx-small', css: 'xx-small', value: 1 }, + { name: 'x-small', css: 'x-small', value: 2 }, + { name: 'small', css: 'small', value: 3 }, + { name: 'medium', css: 'medium', value: 4 }, + { name: 'large', css: 'large', value: 5 }, + { name: 'x-large', css: 'x-large', value: 6 }, + { name: 'xx-large', css: 'xx-large', value: 7 } + ] + }; + toolbarConfig.formatHeader = { + 'display': "
      ", + 'tooltiptext': 'Insert Style', + 'action': function (promise, restoreSelection) { + var format = this.format, + editor = this.$editor(), + $richTextEditor = editor.$parent.$element; + if (!format) { + return promise.resolve(); + } + this.format = undefined; + this.$editor().queryFormatBlockState(format.toLowerCase()); + $richTextEditor.find('[contenteditable]').trigger('click'); + return this.$editor().wrapSelection('formatBlock', '<' + format + '>'); + }, + 'selectFormat': function (event, format) { + this.$parent.format = format; + }, + 'setNodeName': function (el) { + var nodeName; + if (!el) { + this.nodeLabel = this.options.P; + } + nodeName = el.nodeName; + if (nodeName === 'FONT' || nodeName === '#text') { + el = _.get(el, 'parentNode'); + this.setNodeName(el); + } else if (_.includes(_.keys(this.options), nodeName)) { + this.nodeLabel = this.options[nodeName]; + } else { + this.nodeLabel = this.options.P; + } + }, + 'options': { + 'H1': 'Heading 1', + 'H2': 'Heading 2', + 'H3': 'Heading 3', + 'H4': 'Heading 4', + 'P' : 'Paragraph', + 'PRE': 'Pre-formatted' + }, + //activestate is triggered when the caret position changes or any operation is performed on editor, set the button text based on it + 'activeState': function () { + var rangeCount = window.getSelection().rangeCount, + currentEl = rangeCount ? _.get(window.getSelection().getRangeAt(0), 'startContainer') : ''; + + this.setNodeName(currentEl); + + this.$element.find('button span').text(this.nodeLabel); + this.$element.find('button').addClass('active'); + return true; + } + }; + + //set the key mappings for the tab and shift tab combinations when inside the table to perform cell navigation + taOptions.keyMappings = [{ + 'commandKeyCode': 'TabKey', + 'tableNodes': ['TR', 'TD', 'TABLE'], + 'addTableRow': function($el, columnLength) { + var columnTemplate = ' ', + row = WM.element(''); + for (var i =0; i'; + } + + html += ''; + } + + html += ''; + + html += '
      1 x 1
      '; + + return '
      ' + html + '
      '; + } + + function addRow($el) { + var html = '', + x; + + html += ''; + + for (x = 0; x < columnCount; x++) { + html += ''; + } + + html += ''; + + $el.find('table tbody').append(html); + + rowCount++; + } + + function deleteRow($el, y) { + $el.find('table tr:last').remove(); + rowCount--; + if ($el.find('table tr').length > minRows && y + 1 < minRows) { + deleteRow($el, y); + } + } + + function addColumn($el) { + var rows = $el.find('tr'); + + rows.each(function (index, row) { + var anchor = '', + newColEl = row.insertCell(columnCount); + WM.element(newColEl).append(anchor); + }); + + columnCount++; + } + + function deleteColumn($el, x) { + var rows = $el.find('tr'); + + rows.each(function (index, row) { + WM.element(row).find('td:last').remove(); + }); + columnCount--; + + if ($el.find('table tr:first td').length > minColumns && x + 1 < minColumns) { + deleteColumn($el, x); + } + } + + function selectGrid(tx, ty, control) { + var table = WM.element(control).find('table')[0], + x, + y, + focusCell, + cell, + active; + + table.nextSibling.innerHTML = (tx + 1) + ' x ' + (ty + 1); + + for (y = 0; y < rowCount; y++) { + for (x = 0; x < columnCount; x++) { + cell = table.rows[y].childNodes[x].firstChild; + active = x <= tx && y <= ty; + + if (active) { + WM.element(cell).addClass('editor-active'); + focusCell = cell; + } else { + WM.element(cell).removeClass('editor-active'); + } + } + } + + return focusCell.parentNode; + } + + return { + 'restrict': 'A', + 'replace': true, + 'scope': { + 'onSelect': '&' + }, + 'template': function() { + return generateTableGrid(); + }, + 'compile': function () { + return { + 'post': function ($is, $el, attrs) { + $el.on('mouseover', function (e) { + var target = e.target, x, y; + + if (target.tagName.toUpperCase() == 'A') { + x = parseInt(target.getAttribute('data-mce-x'), 10); + y = parseInt(target.getAttribute('data-mce-y'), 10); + + if (x !== this.lastX || y !== this.lastY) { + selectGrid(x, y, $el[0]); + this.lastX = x; + this.lastY = y; + } + //add new rows and columns when user mouseovers the last element + if (y >= minRows - 1 && y + 1 === rowCount) { + addRow($el); + } else if (y < rowCount && rowCount > minRows) { + deleteRow($el, y); + } + if (x >= minColumns - 1 && x + 1 === columnCount) { + addColumn($el); + } else if (x < columnCount && columnCount > minColumns) { + deleteColumn($el, x); + } + } + }); + + $el.on('click', function (e) { + var self = this; + + if (e.target.tagName.toUpperCase() == 'A') { + e.preventDefault(); + e.stopPropagation(); + $is.$parent.selectedRow = { + rows: self.lastY + 1, + columns: self.lastX + 1 + }; + if (WM.isFunction($is.onSelect)) { + $is.onSelect({ + rows: self.lastY + 1, + columns: self.lastX + 1 + }); + } + } + }); + + selectGrid(0, 0, $el[0]); + } + }; + } + }; + }]); /** @@ -182,6 +727,10 @@ WM.module('wm.widgets.form') * This is the default value to be displayed on rich-text-editor widget.
      * Note that the display value is just what the user sees initially, and is not always the dataValue returned by the widget.
      * This is a bindable property. + * @param {string=} htmlcontent + * This is the output value of rich-text-editor widget.
      + * This is the non-sanitized output of the widget. Includes iframe html content.
      + * This is a bindable property. * @param {boolean=} readonly * Selecting this checkbox property prevents the user from being able to change the data value of a widget.
      * Default value: `false`. diff --git a/src/main/webapp/scripts/modules/widgets/form/select/select.js b/src/main/webapp/scripts/modules/widgets/form/select/select.js index 5e4a8d3fc..fcea89cb7 100644 --- a/src/main/webapp/scripts/modules/widgets/form/select/select.js +++ b/src/main/webapp/scripts/modules/widgets/form/select/select.js @@ -19,7 +19,7 @@ WM.module('wm.widgets.form') '' ); }]) - .directive('wmSelect', ['PropertiesFactory', 'WidgetUtilService', 'FormWidgetUtils', 'Utils', function (PropertiesFactory, WidgetUtilService, FormWidgetUtils, Utils) { + .directive('wmSelect', ['PropertiesFactory', 'WidgetUtilService', 'FormWidgetUtils', 'Utils', 'LiveWidgetUtils', function (PropertiesFactory, WidgetUtilService, FormWidgetUtils, Utils, LiveWidgetUtils) { 'use strict'; /*Obtaining properties specific to select widget by extending from all editor related widget properties*/ @@ -93,7 +93,12 @@ WM.module('wm.widgets.form') /* to check if the function is not triggered from onChangeProxy */ if (!_modelChangedManually[scope.$id]) { if (scope.datafield !== ALLFIELDS) { - _modelProxy = WM.isObject(_model_) ? _model_ : WM.isDefined(_model_) && _.toString(_model_); + if (WM.isDefined(_model_)) { + _modelProxy = WM.isObject(_model_) ? _model_ : _.toString(_model_); + } else { + _modelProxy = undefined; + } + scope.modelProxy = _modelProxy; if (WM.isDefined(_modelProxy)) { setDisplayValFromModelProxy(scope, _modelProxy); @@ -380,6 +385,12 @@ WM.module('wm.widgets.form') iScope.$watch('scopedataset', scopeDatasetWatcher.bind(undefined, iScope, element)); } } + //In run mode, If widget is bound to selecteditem subset, fetch the data dynamically + if (!attrs.widgetid && _.includes(iScope.binddataset, 'selecteditem.')) { + LiveWidgetUtils.fetchDynamicData(iScope, scope, function (data) { + createSelectOptions(data, iScope, element); + }); + } } } }; diff --git a/src/main/webapp/scripts/modules/widgets/form/switch/switch.js b/src/main/webapp/scripts/modules/widgets/form/switch/switch.js index 1d2244555..78b6ef1ee 100644 --- a/src/main/webapp/scripts/modules/widgets/form/switch/switch.js +++ b/src/main/webapp/scripts/modules/widgets/form/switch/switch.js @@ -12,7 +12,7 @@ WM.module('wm.widgets.form') '
      ' + '{{opt[displayfield || "label"]}}' + + ' ng-click="selectOpt($event, $index)">{{opt[displayfield] || opt["label"]}}' + '
      ' + '{{options[selected.index][displayfield || "label"] || _model_}}' + '' + @@ -25,8 +25,9 @@ WM.module('wm.widgets.form') 'WidgetUtilService', 'FormWidgetUtils', 'Utils', + 'LiveWidgetUtils', - function (PropertiesFactory, WidgetUtilService, FormWidgetUtils, Utils) { + function (PropertiesFactory, WidgetUtilService, FormWidgetUtils, Utils, LiveWidgetUtils) { 'use strict'; var widgetProps = PropertiesFactory.getPropertiesOf('wm.switch', ['wm.base', 'wm.base.editors.abstracteditors']), @@ -55,7 +56,8 @@ WM.module('wm.widgets.form') function setSelectedValue(scope) { var options = scope.options; - if (WM.isDefined(scope._model_)) { + //If _model_ is defined and is not empty string, then set selected index (_model_ can be 0) + if (WM.isDefined(scope._model_) && _.trim(scope._model_).length) { options.some(function (opt, index) { if (_.isEqual(scope._model_, opt) @@ -220,6 +222,12 @@ WM.module('wm.widgets.form') updateSwitchOptions(scope, element, newVal); }); } + //In run mode, If widget is bound to selecteditem subset, fetch the data dynamically + if (!attrs.widgetid && _.includes(scope.binddataset, 'selecteditem.')) { + LiveWidgetUtils.fetchDynamicData(scope, element.scope(), function (data) { + updateSwitchOptions(scope, element, data); + }); + } } } }; diff --git a/src/main/webapp/scripts/modules/widgets/form/time/time.js b/src/main/webapp/scripts/modules/widgets/form/time/time.js index adb4e245c..2933eba2e 100644 --- a/src/main/webapp/scripts/modules/widgets/form/time/time.js +++ b/src/main/webapp/scripts/modules/widgets/form/time/time.js @@ -6,8 +6,8 @@ WM.module('wm.widgets.form') 'use strict'; $templateCache.put('template/widget/form/time.html', '