├── LICENSE ├── README.md └── src └── aura ├── lightningCore ├── lightningCore.cmp ├── lightningCore.cmp-meta.xml ├── lightningCoreController.js └── lightningCoreHelper.js └── lightningCoreModule ├── lightningCoreModule.intf └── lightningCoreModule.intf-meta.xml /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 4an70m 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lightning-core 2 | 3 | ## What is this? 4 | lightning-core is a small library to use features of lightning in es6 style. It allows to eliminate the most common boilerplate code such as: 5 | - Toasts 6 | - Server calling 7 | - Creating components, modals 8 | - Downloading files 9 | - Working with local/session storage 10 | - Working with libraries (overlay, navigation, notification) 11 | - Extension Modules 12 | 13 | ## Why do I need this? 14 | Lots of projects I've ran into has either custom library-like component or JS resource file or they are stuck with each component defining a "new solution" for each pain-point, e.g. method for creating a Toast in every single component. 15 | The purpose of this library component is to stop this madness. 16 | 17 | ## Lightning? 18 | Lightnig is still something viable for a new project or for refactoring an old one and, I guess, will be for a couple more years, considering how many beautiful and functional things are already created for it. 19 | 20 | ## How to use? 21 | The library is really simple and intuitive to use. You add it onse in the top level component, it immutably writes itself into the window object and after that you can use it anywhere. The variable in the window object is setted only once in a singleton manner, so don't worry if you reference the library somwhere else in your component hierarchy - the top-level lightning-core component will initiallize first. 22 | 23 | Start with: 24 | ```html 25 | 26 | 27 | 28 | 29 | ``` 30 | And now you can use it anywhere in your project! 31 | 32 | 33 | ## Architecture 34 | Everything in this library is a class. Each use-case of lightning-core is represented by a single class. The classes are combined into a variable, that serves as an export list and then attached to a window object as a namespace. 35 | E.g. 36 | ```javascript 37 | new window.core.Toast().fire(); 38 | ``` 39 | Example shows how you can access one of the exported classes - `Toast` and fire it. But you may also skip window in your reference to the class: 40 | ```javascript 41 | new core.Toast().fire(); 42 | ``` 43 | 44 | ### Use-cases: Toast 45 | There are a number of classes, which are designed to ease the way of creating Toasts: 46 | 47 | ```javascript 48 | /*base toast*/ 49 | new core.Toast({ 50 | /*standard toast params*/ 51 | }).fire(); 52 | 53 | /*defaults mode to dismissable, time to 4s*/ 54 | new core.ToastQuick(type, title, message).fire(); 55 | 56 | /*defaults mode to dismissable, time to 8s*/ 57 | new core.ToastLong(type, title, message).fire(); 58 | 59 | 60 | /*defaults mode to dismissable, type to success, title to Success!, time to 4s*/ 61 | new core.ToastQuickSuccess(type, title, message).fire(); 62 | 63 | /*defaults mode to dismissable, type to error, title to Something went wrong!, time to 4s*/ 64 | new core.ToastQuickError(type, title, message).fire(); 65 | 66 | /*defaults mode to dismissable, type to success, title to Success!, time to 8s*/ 67 | new core.ToastLongSuccess(type, title, message).fire(); 68 | 69 | /*defaults mode to dismissable, type to error, title to Something went wrong!, time to 8s*/ 70 | new core.ToastLongError(type, title, message).fire(); 71 | ``` 72 | The classes can be configured for each project independenlty and updated with default time, message, etc. 73 | 74 | ### Use-cases: Server calling 75 | Several classes are dedicated to perform server calls with or without Promises as well as providing a way to parse error messages and autohandle errors. 76 | 77 | ```javascript 78 | /*designed to perform promise-like async operations of calling the server, but that can be used with @AuraEnabled(cacheable=true). However, this cannot be chained like an actual promise*/ 79 | new core.ServerAction(component, actionName, params).execute() 80 | .then(result => { 81 | //result handling 82 | }) 83 | .catch(error => { 84 | //error handling 85 | }) 86 | .finally(() => { 87 | //some operation regardles of result 88 | }) 89 | 90 | /*default promise, that is designed to perform server calling, but that doesnt require to be wrapped with $A.getCallback(...)*/ 91 | new core.ServerActionPromise(component, actionName, params).execute() 92 | .then(result => { 93 | //result handling 94 | }) 95 | .catch(error => { 96 | //error handling 97 | }) 98 | .finally(() => { 99 | //some operation regardles of result 100 | }) 101 | 102 | /*...Handled classes are similar to the same classes without Handled, except for they automatically parse an error from response and show a toast with core.ToastLongError class*/ 103 | new core.ServerActionHandled()/*...*/ 104 | new core.ServerActionPromiseHandled()/*...*/ 105 | ``` 106 | 107 | ### Use-cases: Components 108 | With lightning-core dynamically creating new components is designed to be intuitive and easy - no need to check documentation every time you need a dynamically generated component. 109 | 110 | ```javascript 111 | /*each dynamic component is represented with a single instance of core.Component class and onyl requires a name and desired attributes. .create() method returns a Promsie*/ 112 | new core.Component(name, params).create() 113 | .then((component) => { 114 | /*do smth with newly generated component*/ 115 | }) 116 | .catch((error) => { 117 | /*handle errors*/ 118 | }); 119 | 120 | /*there's also a way to create component in bulk*/ 121 | new core.Components() 122 | .addComponent(new core.Component(name, params)) 123 | .addComponent(new core.Component(name, params)) 124 | .addComponent(new core.Component(name, params)) 125 | .create() 126 | .then((components) => { 127 | /*do smth with newly generated components*/ 128 | }) 129 | .catch((error) => { 130 | /*handle errors*/ 131 | }); 132 | ``` 133 | 134 | ### Use-cases: Modals 135 | Creating modals is usually also a drag. With lightning-core creating modals is as easy as creating component. But! Modals are created based on the `lightning:overlayLibrary`. To use this library in lightning-core component you need to include this library into your instance of the library - simply include a library instance into the markup between opening and closing tags of ``: 136 | 137 | ```html 138 | 139 | 140 | 141 | 142 | 143 | 144 | ``` 145 | After that, you can use any supported functionality for the library, for example - Modal creation. 146 | ```javascript 147 | new core.Modal() 148 | .setBody(new Component(name, params)) 149 | .setFooter(new Component(name, params)) 150 | .show() 151 | .then((overlay) => { 152 | /*success callback*/ 153 | }) 154 | .catch((error) => { 155 | /*error callback*/ 156 | }); 157 | ``` 158 | 159 | ### Use-cases: Files 160 | There's a small class for downloading files, instantiated from base64 or Blob. 161 | ```javascript 162 | /*new file*/ 163 | new core.File(base64Data); 164 | new core.File(blobData); 165 | 166 | /*converting file*/ 167 | new core.File(blobData).toBase64(); 168 | new core.File(base64Data).toBlob(); 169 | 170 | /*downlaoding*/ 171 | new core.File(base64Data).download(filename); 172 | new core.File(blobData).download(filename); 173 | ``` 174 | 175 | ### Use-cases: Local/Session Storage 176 | There's a small class for working with local or session storage. 177 | ```javascript 178 | const storage = new core.LocalStorage(); 179 | //or new core.SessionStorage() - can be used instead, both classes has identical interface 180 | storage.set('key1', 'value'); 181 | storage.setObject('key2', {'object-key': 'object-value'}); //performs JSON.stringify(...) 182 | 183 | storage.get('key1'); 184 | storage.getObject('key2'); //performs JSON.parse(...) 185 | 186 | storage.clear(); 187 | storage.print(); 188 | ``` 189 | 190 | ### Use-cases: Libraries 191 | As was mentioned before in the Modals section, to work with a library and it's functionality you need to include library markup into the body of lightning-core component: 192 | ```html 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | ``` 203 | Functionality which is supported through these libraries: 204 | - Page reference navigation (lightning:navigation) 205 | - Notices and Toasts (lightning:notificationsLibrary) 206 | - Modals and Popovers (lightning:overlayLibrary) 207 | 208 | Examples: 209 | ```javascript 210 | /*Navigation*/ 211 | const navigation = new core.Navigation(); 212 | navigation.navigate(new core.PageReferenceWebPage().setUrl(url)); 213 | 214 | /*Notice*/ 215 | new core.Notice() 216 | .setTitle(title) 217 | .setMessage(message) 218 | .show(); 219 | 220 | /*Toast - toasts has the same classes, as regular toast, but it has X in it's name. E.g.*/ 221 | new core.ToastXLongError(message).fire(); 222 | 223 | /*Modal*/ 224 | new core.Modal() 225 | .setBody(new Component(name, params)) 226 | .setFooter(new Component(name, params)) 227 | .show() 228 | .then((overlay) => { 229 | /*success callback*/ 230 | }) 231 | .catch((error) => { 232 | /*error callback*/ 233 | }); 234 | 235 | /*Popover*/ 236 | new core.Popover() 237 | .setBody(body) 238 | .setReferenceSelector(referenceElementSelector) 239 | .show() 240 | .then((overlay) => { 241 | /*success callback*/ 242 | }) 243 | .catch((error) => { 244 | /*error callback*/ 245 | }); 246 | ``` 247 | ### Use-case: Extension Modules 248 | If you would like to add new functions, custom libraries or even submodules to the lightning-core library, you can easily do this, by implementing lightningCoreModule interface in your custom component, writing logic for export() method and including your custom component into lightning-core body. 249 | First, create a module and implement interface and export method: 250 | ```html 251 | 252 | 253 | 254 | ``` 255 | 256 | ```javascript 257 | ({ 258 | //Controller 259 | //implementation of interface method 260 | export: function(cmp, evt, helper) { 261 | return helper.exportClasses(); 262 | }, 263 | 264 | //Helper 265 | exportClasses: function() { 266 | class AlertToast { 267 | constructor(message) { 268 | this.message = message; 269 | } 270 | 271 | fire() { 272 | alert(this.message); 273 | } 274 | } 275 | 276 | //returning value should always be an array in this format: 277 | //[module_name, {exported_classes}] 278 | return ['alerts', { 279 | "AlertToast": AlertToast 280 | }]; 281 | }, 282 | }); 283 | ``` 284 | Finally, after you include your module into lightning-core body, you may start using it everywhere lightning-core is available: 285 | ```html 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | ``` 294 | Usage: 295 | ```javascript 296 | new core.alerts.AlertToast('My message').fire(); 297 | ``` 298 | Lightning-core will perform a check if you've implemented export() method, if you've returned a propperly formatted value and if your submodule name overwrites any other functions/submodules. If any error occures, lightning-core will display an error message in error logs. 299 | 300 | ## ToDo 301 | - Complete readme 302 | - Complete JS Docs 303 | - Add more functions 304 | -------------------------------------------------------------------------------- /src/aura/lightningCore/lightningCore.cmp: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/aura/lightningCore/lightningCore.cmp-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 46.0 4 | lightningCore 5 | 6 | -------------------------------------------------------------------------------- /src/aura/lightningCore/lightningCoreController.js: -------------------------------------------------------------------------------- 1 | ({ 2 | /** 3 | * Init function for component to load lightning-core library 4 | * 5 | * @version 1.0 6 | * @author github/4an70m 7 | * @param cmp 8 | * @param evt 9 | * @param helper 10 | */ 11 | doInit: function(cmp, evt, helper) { 12 | helper.core(cmp).init(); 13 | } 14 | }); -------------------------------------------------------------------------------- /src/aura/lightningCore/lightningCoreHelper.js: -------------------------------------------------------------------------------- 1 | ({ 2 | 3 | /** 4 | * Function to import lightning-core into the window object.
5 | * Returns init() function, which should be explicitly called. 6 | * 7 | * @version 1.0 8 | * @author github/4an70m 9 | * @param cmp 10 | * @returns {{init}} 11 | */ 12 | core: function (cmp) { 13 | 14 | const component = cmp; 15 | 16 | /** 17 | * Class for checking runtime environment. 18 | * Current implementations relays on if global toast event is supported. 19 | */ 20 | class Environment { 21 | 22 | constructor() { 23 | //currently there's no better way to find out the runtime environment 24 | this.environment = $A.util.isUndefinedOrNull($A.get("e.force:showToast")) ? "App" : "Lightning"; 25 | } 26 | 27 | /** 28 | * Checks if current environment is an Application 29 | * @returns {boolean} 30 | */ 31 | isApp() { 32 | return this.environment === "App"; 33 | } 34 | 35 | /** 36 | * Checks if current environment is a lightning org runtime 37 | * @returns {boolean} 38 | */ 39 | isLightning() { 40 | return this.environment === "Lightning"; 41 | } 42 | } 43 | 44 | 45 | /* classes for creating toasts */ 46 | /** 47 | * Simple class for creation toasts.
48 | * Use this format:
49 | * 50 | * new core.Toast({...}).fire(); 51 | * 52 | */ 53 | class Toast { 54 | 55 | constructor(params = {}) { 56 | this.params = params; 57 | } 58 | 59 | /** 60 | * Sets the type of the toast. Accepted values are:
61 | * 67 | * 68 | * @param type 69 | * @returns {Toast} 70 | */ 71 | setType(type) { 72 | this.params.type = type; 73 | return this; 74 | } 75 | 76 | /** 77 | * Sets the title of the toast. 78 | * 79 | * @param title 80 | * @returns {Toast} 81 | */ 82 | setTitle(title) { 83 | this.params.title = title; 84 | return this; 85 | } 86 | 87 | /** 88 | * Sets the message of the toast. 89 | * 90 | * @param message 91 | * @returns {Toast} 92 | */ 93 | setMessage(message) { 94 | this.params.message = message; 95 | return this; 96 | } 97 | 98 | /** 99 | * Sets the mode of the toast. 100 | * Supported values are: 101 | * 106 | * 107 | * @param mode 108 | * @returns {Toast} 109 | */ 110 | setMode(mode) { 111 | this.params.mode = mode; 112 | return this; 113 | } 114 | 115 | /** 116 | * Sets the duration of the toast. 117 | * 118 | * @param duration 119 | * @returns {Toast} 120 | */ 121 | setDuration(duration) { 122 | this.params.duration = duration; 123 | return this; 124 | } 125 | 126 | /** 127 | * Fires a toast
128 | * Displays an error in console, if toast is not supported in this environment 129 | */ 130 | fire() { 131 | try { 132 | if (new Environment().isApp()) { 133 | throw new Error("$A.e.force:showToast is not supported in App"); 134 | } 135 | const toastEvent = $A.get("e.force:showToast"); 136 | toastEvent.setParams(this.params); 137 | toastEvent.fire(); 138 | } catch (e) { 139 | console.error(`Core:\nRunning e.force:showToast raised an exception:\n${e}`) 140 | } 141 | } 142 | } 143 | 144 | /** 145 | * Toast with predefined mode = "dismissible" and duration = 4s
146 | * Use this format:
147 | * 148 | * new core.ToastQuick(type, title, message).fire(); 149 | * 150 | */ 151 | class ToastQuick extends Toast { 152 | 153 | constructor(type, title, message) { 154 | super({ 155 | "type": type, 156 | "title": title, 157 | "message": message, 158 | "mode": "dismissible", 159 | "duration": 4000 160 | }); 161 | } 162 | } 163 | 164 | /** 165 | * Toast with predefined mode = "dismissible" and duration = 8s
166 | * Use this format:
167 | * 168 | * new core.ToastLong(type, title, message).fire(); 169 | * 170 | */ 171 | class ToastLong extends Toast { 172 | 173 | constructor(type, title, message) { 174 | super({ 175 | "type": type, 176 | "title": title, 177 | "message": message, 178 | "mode": "dismissible", 179 | "duration": 8000 180 | }); 181 | } 182 | } 183 | 184 | /** 185 | * Toast with predefined 186 | *
mode = "dismissible" 187 | *
duration = 4s 188 | *
type = "success" 189 | *
title = "Success!" 190 | *
191 | * Use this format:
192 | * 193 | * new core.ToastQuickSuccess(message).fire(); 194 | * 195 | */ 196 | class ToastQuickSuccess extends ToastQuick { 197 | 198 | constructor(message) { 199 | super( 200 | "success", 201 | "Success!", 202 | message 203 | ); 204 | } 205 | } 206 | 207 | /** 208 | * Toast with predefined 209 | *
mode = "dismissible" 210 | *
duration = 4s 211 | *
type = "error" 212 | *
title = "Something went wrong!" 213 | *
214 | * Use this format:
215 | * 216 | * new core.ToastQuickError(message).fire(); 217 | * 218 | */ 219 | class ToastQuickError extends ToastQuick { 220 | 221 | constructor(message) { 222 | super( 223 | "error", 224 | "Something went wrong!", 225 | message 226 | ); 227 | } 228 | } 229 | 230 | /** 231 | * Toast with predefined 232 | *
mode = "dismissible" 233 | *
duration = 8s 234 | *
type = "success" 235 | *
title = "Success!" 236 | *
237 | * Use this format:
238 | * 239 | * new core.ToastLongSuccess(message).fire(); 240 | * 241 | */ 242 | class ToastLongSuccess extends ToastLong { 243 | 244 | constructor(message) { 245 | super( 246 | "success", 247 | "Success!", 248 | message 249 | ); 250 | } 251 | } 252 | 253 | /** 254 | * Toast with predefined 255 | *
mode = "dismissible" 256 | *
duration = 8s 257 | *
type = "error" 258 | *
title = "Something went wrong!" 259 | *
260 | * Use this format:
261 | * 262 | * new core.ToastLongError(message).fire(); 263 | * 264 | */ 265 | class ToastLongError extends ToastLong { 266 | 267 | constructor(message) { 268 | super( 269 | "error", 270 | "Something went wrong!", 271 | message 272 | ); 273 | } 274 | } 275 | 276 | 277 | /* classes for server interactions */ 278 | /** 279 | * Promise-like class for server action calling.
280 | * Doesn't support chaining yet. 281 | * Use this format:
282 | * 283 | * new core.ServerAction(component, actionName, (opt) params).execute(); 284 | * 285 | */ 286 | class ServerAction { 287 | 288 | constructor(component, action, params) { 289 | this.action = ServerAction.getAction(component, action, params); 290 | } 291 | 292 | /** 293 | * Default message for parseResponseMessage() method, when the message cannot be parsed
294 | *
Replace default message this with appropriate label, if you support multiple languages 295 | * 296 | * @see ServerAction.parseResponseMessage 297 | * @returns {string} 298 | */ 299 | static get messageUndefinedResponse() { 300 | return "Undefined response"; 301 | } 302 | 303 | /** 304 | * Default message for parseResponseMessage() method, when the error is unknown 305 | *
Replace default message this with appropriate label, if you support multiple languages 306 | * 307 | * @see ServerAction.parseResponseMessage 308 | * @returns {string} 309 | */ 310 | static get messageUnknownError() { 311 | return "Unknown error"; 312 | } 313 | 314 | /** 315 | * Default message for parseResponseMessage() method, when action was interrupted 316 | *
Replace default message this with appropriate label, if you support multiple languages 317 | * 318 | * @see ServerAction.parseResponseMessage 319 | * @returns {string} 320 | */ 321 | static get messageIncompleteAction() { 322 | return "No response from server or client is offline"; 323 | } 324 | 325 | /** 326 | * Default message for parseResponseMessage() method, when the error was unexpected and no other error was applicable 327 | *
Replace default message this with appropriate label, if you support multiple languages 328 | * 329 | * @see ServerAction.parseResponseMessage 330 | * @returns {string} 331 | */ 332 | static get messageUnexpectedError() { 333 | return "Unexpected error"; 334 | } 335 | 336 | /** 337 | * Method for getting apex-based action.
338 | * Supports action name without "c." prefix.
339 | * params parameter is optional and can be omitted.
340 | * 341 | *
Displays an error in console if the action was not found. 342 | * 343 | * @param cmp 344 | * @param actionName 345 | * @param params 346 | * @returns {*} 347 | */ 348 | static getAction(cmp, actionName, params) { 349 | if (actionName.indexOf("c.") <= -1) { 350 | actionName = "c." + actionName; 351 | } 352 | let action = null; 353 | 354 | try { 355 | action = cmp.get(actionName); 356 | } catch (error) { 357 | console.error(`\nCore:\n${actionName} is invalid action.\n + ${error}`); 358 | return action; 359 | } 360 | 361 | if (!$A.util.isUndefinedOrNull(params)) { 362 | action.setParams(params); 363 | } 364 | return action; 365 | } 366 | 367 | /** 368 | * Executes an action. Performs a server call. 369 | * 370 | * @returns {LightningAction} 371 | */ 372 | execute() { 373 | return new LightningAction((context, success, error) => { 374 | this.action.setCallback(this, result => { 375 | let state = result.getState(); 376 | if (state === "SUCCESS") { 377 | success(context, result.getReturnValue()); 378 | } else { 379 | error(context, result); 380 | } 381 | }); 382 | $A.enqueueAction(this.action); 383 | }); 384 | } 385 | 386 | /** 387 | * Multipurpose method, which parses any response, that lightning actions might though 388 | * 389 | * @param response 390 | * @returns {*} 391 | */ 392 | static parseResponseMessage(response) { 393 | if ($A.util.isUndefinedOrNull(response)) { 394 | return ServerAction.messageUndefinedResponse; 395 | } 396 | 397 | if (typeof response === "string") { 398 | return response; 399 | } 400 | 401 | if (response.message) { 402 | return response.message; 403 | } 404 | 405 | if (response.getState) { 406 | const state = response.getState(); 407 | let message = ServerAction.messageUnknownError; 408 | if (state === "ERROR") { 409 | let errors = response.getError(); 410 | if (errors && errors[0] && errors[0].message) { 411 | message = errors[0].message; 412 | } 413 | if (errors && errors[0] && errors[0].pageErrors && errors[0].pageErrors[0]) { 414 | message = errors[0].pageErrors[0].message; 415 | } 416 | } else if (state === "INCOMPLETE") { 417 | message = ServerAction.messageIncompleteAction; 418 | } 419 | return message; 420 | } 421 | return ServerAction.messageUnexpectedError; 422 | } 423 | } 424 | 425 | /** 426 | * Promise-like class for server action calling.
427 | * Automatically handles an error with core.ToastLongError(...)
428 | * Doesn't support chaining yet. 429 | * Use this format:
430 | * 431 | * new core.ServerActionHandled(component, actionName, (opt) params).execute(); 432 | * 433 | */ 434 | class ServerActionHandled extends ServerAction { 435 | 436 | constructor(component, action, params) { 437 | super(component, action, params); 438 | } 439 | 440 | /** 441 | * Executes an action. Performs a server call
442 | * If the action fails, a ToastLongError will be thrown. 443 | * 444 | * @see ToastLongError 445 | * @returns {LightningAction} 446 | */ 447 | execute() { 448 | return new LightningAction((context, success, error) => { 449 | this.action.setCallback(this, result => { 450 | let state = result.getState(); 451 | if (state === "SUCCESS") { 452 | success(context, result.getReturnValue()); 453 | } else { 454 | new ToastLongError(ServerAction.parseResponseMessage(result)); 455 | error(context, result); 456 | } 457 | }); 458 | $A.enqueueAction(this.action); 459 | }); 460 | } 461 | } 462 | 463 | /** 464 | * Promise class for server action calling.
465 | * Use this format:
466 | * 467 | * new core.ServerActionPromise(component, actionName, (opt) params).execute(); 468 | * 469 | */ 470 | class ServerActionPromise extends ServerAction { 471 | 472 | constructor(component, action, params) { 473 | super(component, action, params); 474 | } 475 | 476 | /** 477 | * Executes an action. Performs a Promise server call. 478 | * 479 | * @returns {LightningAction} 480 | */ 481 | execute() { 482 | return new LightningPromise((resolve, reject) => { 483 | this.action.setCallback(this, result => { 484 | let state = result.getState(); 485 | if (state === "SUCCESS") { 486 | resolve(result.getReturnValue()); 487 | } else { 488 | reject(result); 489 | } 490 | }); 491 | $A.enqueueAction(this.action); 492 | }); 493 | } 494 | } 495 | 496 | /** 497 | * Promise class for server action calling.
498 | * Automatically handles an error with core.ToastLongError(...)
499 | * Use this format:
500 | * 501 | * new core.ServerActionPromiseHandled(component, actionName, (opt) params).execute(); 502 | * 503 | */ 504 | class ServerActionPromiseHandled extends ServerAction { 505 | 506 | constructor(component, action, params) { 507 | super(component, action, params); 508 | } 509 | 510 | /** 511 | * Executes an action. Performs a Promise server call
512 | * If the action fails, a ToastLongError will be thrown. 513 | * 514 | * @see ToastLongError 515 | * @returns {LightningAction} 516 | */ 517 | execute() { 518 | return new LightningPromise((resolve, reject) => { 519 | this.action.setCallback(this, result => { 520 | let state = result.getState(); 521 | if (state === "SUCCESS") { 522 | resolve(result.getReturnValue()); 523 | } else { 524 | new ToastLongError(ServerAction.parseResponseMessage(result)).fire(); 525 | reject(result); 526 | } 527 | }); 528 | $A.enqueueAction(this.action); 529 | }); 530 | } 531 | } 532 | 533 | /** 534 | * Promise substitute class.
535 | * Built with a single purpose - to execute server actions in a Promise-like manner for cacheable=true actions 536 | */ 537 | class LightningAction { 538 | 539 | constructor(action) { 540 | this.action = action; 541 | this._resolve(); 542 | } 543 | 544 | /** 545 | * Adds a handler function for success callback 546 | * 547 | * @param onSuccess 548 | * @returns {LightningAction} 549 | */ 550 | then(onSuccess) { 551 | this.onSuccess = onSuccess; 552 | return this; 553 | } 554 | 555 | /** 556 | * Adds a handler function for error callback 557 | * 558 | * @param onError 559 | * @returns {LightningAction} 560 | */ 561 | catch(onError) { 562 | this.onError = onError; 563 | return this; 564 | } 565 | 566 | /** 567 | * Adds a handler function for any outcome callback 568 | * 569 | * @param onFinally 570 | * @returns {LightningAction} 571 | */ 572 | finally(onFinally) { 573 | this.onFinally = onFinally; 574 | return this; 575 | } 576 | 577 | _success(self, result) { 578 | try { 579 | if (self.onSuccess) { 580 | self.onSuccess(result); 581 | } 582 | if (self.onFinally) { 583 | self.onFinally(); 584 | } 585 | } catch (e) { 586 | self._error(self, e); 587 | } 588 | } 589 | 590 | _error(self, error) { 591 | if (self.onError) { 592 | self.onError(error); 593 | } else { 594 | console.error(`Core:\nUnhandled error in Lightning Action: ${error}\n`); 595 | } 596 | if (self.onFinally) { 597 | self.onFinally(); 598 | } 599 | } 600 | 601 | _resolve() { 602 | const self = this; 603 | window.setTimeout($A.getCallback(() => { 604 | this.action(self, this._success, this._error); 605 | }, 0)); 606 | } 607 | } 608 | 609 | /** 610 | * Child class of Promise class.
611 | * Wraps most of the common functions with $A.getCallback(...) to eliminate the need of wrapping callback functions 612 | */ 613 | class LightningPromise extends Promise { 614 | 615 | constructor(fn) { 616 | super($A.getCallback(fn)); 617 | } 618 | 619 | /** 620 | * Adds a handler function on Success and Error outcome 621 | * 622 | * @param onSuccess 623 | * @param onError 624 | * @returns {Promise} 625 | */ 626 | then(onSuccess, onError) { 627 | return super.then( 628 | (onSuccess ? $A.getCallback(onSuccess) : undefined), 629 | (onError ? $A.getCallback(onError) : undefined) 630 | ); 631 | } 632 | 633 | /** 634 | * Adds a handle function on Error outcome 635 | * 636 | * @param onError 637 | * @returns {Promise} 638 | */ 639 | catch(onError) { 640 | return super.catch( 641 | onError ? $A.getCallback(onError) : undefined 642 | ); 643 | } 644 | 645 | /** 646 | * Adds a handler function on any outcome 647 | * 648 | * @param onFinally 649 | * @returns {Promise} 650 | */ 651 | finally(onFinally) { 652 | return super.finally( 653 | onFinally ? $A.getCallback(onFinally) : undefined 654 | ); 655 | } 656 | } 657 | 658 | 659 | /* classes for component creation*/ 660 | /** 661 | * Class, which represents a container for a single component
662 | * Supports creation of the component with a Promise 663 | * 664 | * @see LightningPromise 665 | */ 666 | class Component { 667 | 668 | constructor(name, params = {}) { 669 | this.name = name; 670 | this.params = params; 671 | } 672 | 673 | /** 674 | * Adds a parameter to component 675 | * 676 | * @param name 677 | * @param value 678 | * @returns {Component} 679 | */ 680 | addParam(name, value) { 681 | this.params[name] = value; 682 | return this; 683 | } 684 | 685 | /** 686 | * Removes a parameter from component 687 | * @param name 688 | * @returns {Component} 689 | */ 690 | removeParam(name) { 691 | delete this.params[name]; 692 | return this; 693 | } 694 | 695 | /** 696 | * Converts component to the format, which is acceptable for $A.createComponents 697 | * @returns {*[]} 698 | */ 699 | toParams() { 700 | return [ 701 | this.name, 702 | this.params 703 | ]; 704 | } 705 | 706 | /** 707 | * Creates a component using LightningPromise 708 | * 709 | * @see LightningPromise 710 | * @returns {LightningPromise} 711 | */ 712 | create() { 713 | return new LightningPromise((resolve, reject) => { 714 | $A.createComponent(this.name, this.params, (components, status, errorMessage) => { 715 | if (status === "SUCCESS") { 716 | resolve(components); 717 | } else { 718 | reject(errorMessage, status); 719 | } 720 | }); 721 | }); 722 | } 723 | } 724 | 725 | /** 726 | * Container class for bulk-creation of components 727 | */ 728 | class Components { 729 | 730 | constructor() { 731 | this.components = []; 732 | } 733 | 734 | /** 735 | * Adds a component to the container 736 | * 737 | * @param component 738 | * @returns {Components} 739 | */ 740 | addComponent(component) { 741 | if (component instanceof Component) { 742 | this.components.push(component); 743 | } 744 | return this; 745 | } 746 | 747 | /** 748 | * Performs bulk creation of components
749 | * Returns null if there's no components to create
750 | * Returns LightningPromise if there are components to create
751 | * 752 | * @see LightningPromise 753 | * @returns {*} 754 | */ 755 | create() { 756 | if (this.components.length === 0) { 757 | return null; 758 | } 759 | const params = this.components.map((component) => { 760 | return component.toParams(); 761 | }); 762 | return new LightningPromise((resolve, reject) => { 763 | $A.createComponents(params, (components, status, errorMessage) => { 764 | if (status === "SUCCESS") { 765 | resolve(components); 766 | } else { 767 | reject(errorMessage, status); 768 | } 769 | }); 770 | }); 771 | } 772 | } 773 | 774 | 775 | /* classes for working with files */ 776 | /** 777 | * Files wrapper class, built for convenient conversion and downloading of a file 778 | */ 779 | class File { 780 | 781 | /** 782 | * Accepts Base64 or Blob representation of a file 783 | * 784 | * @param fileData 785 | * @param fileName 786 | */ 787 | constructor(fileData, fileName = "download") { 788 | this.fileData = fileData; 789 | this.fileName = fileName; 790 | } 791 | 792 | /** 793 | * Converts file to Base64 format 794 | * 795 | * @returns {LightningPromise} 796 | */ 797 | toBase64() { 798 | return new LightningPromise((resolve, reject) => { 799 | const reader = new FileReader(); 800 | reader.readAsDataURL(this.fileData); 801 | reader.onloadend = (evt) => { 802 | const error = evt.target.error; 803 | if (!$A.util.isUndefinedOrNull(error)) { 804 | reject(error); 805 | return; 806 | } 807 | resolve(evt.target.result); 808 | }; 809 | }); 810 | } 811 | 812 | /** 813 | * Converts file to Blob format 814 | * 815 | * @returns {Blob} 816 | */ 817 | toBlob() { 818 | let sliceSize = 512; 819 | let byteCharacters = atob(this.fileData); 820 | let byteArrays = []; 821 | for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) { 822 | let slice = byteCharacters.slice(offset, offset + sliceSize); 823 | let byteNumbers = new Array(slice.length); 824 | for (var i = 0; i < slice.length; i++) { 825 | byteNumbers[i] = slice.charCodeAt(i); 826 | } 827 | let byteArray = new Uint8Array(byteNumbers); 828 | byteArrays.push(byteArray); 829 | } 830 | return new Blob(byteArrays, {type: "application/octet-stream"}); 831 | } 832 | 833 | /** 834 | * Performs a client-side downloading of a file 835 | * 836 | * @param fileName 837 | */ 838 | download(fileName) { 839 | const a = document.createElement("a"); 840 | a.download = this.fileName || fileName; 841 | a.rel = "noopener"; 842 | a.target = "_blank"; 843 | 844 | if (this.fileData instanceof Blob) { 845 | a.href = window.URL.createObjectURL(this.fileData); 846 | } else { 847 | a.href = this.toBlob(); 848 | } 849 | a.click(); 850 | 851 | } 852 | } 853 | 854 | 855 | /* library class */ 856 | /** 857 | * Class, which represents a dependency to a specific library
858 | * Retrieves it from the body and checks if there's a library
859 | * Throws specific errors, if there's no library attached 860 | */ 861 | class Library { 862 | 863 | constructor(name) { 864 | const body = component.get("v.body") || []; 865 | this.library = body.find((component) => { 866 | return component.isInstanceOf(name); 867 | }); 868 | if (!this.library) { 869 | const message = `Core:\nTo use ${this.constructor.name}, please include ${name} component inside c:lightningCore.\n`; 870 | console.error(message); 871 | throw new Error(message); 872 | } 873 | } 874 | } 875 | 876 | 877 | /* classes to work with lightning:navigation */ 878 | /** 879 | * Navigation library, wraps lightning:navigation
880 | * Supports old-style navigation through "e.force:navigateTo..." as well as PageReference navigations 881 | */ 882 | class Navigation extends Library { 883 | 884 | constructor() { 885 | super("lightning:navigation"); 886 | } 887 | 888 | /** 889 | * Navigation with a PageReference classes
890 | * Supports a preNavigateCallback function, which is executed after url is generated, but before the navigation occurs.
891 | * 892 | * @see PageReference 893 | * @param pageReference 894 | * @param preNavigateCallback 895 | */ 896 | navigate(pageReference) { 897 | if (!(pageReference instanceof PageReference)) { 898 | const message = `Core:\nnavigate() method should be called with PageReference parameter\n`; 899 | console.error(message); 900 | throw new Error(message); 901 | } 902 | this.library.navigate(pageReference.toPageReference()) 903 | .catch($A.getCallback((error) => { 904 | console.error(`Core:\nUrl generation encountered an error: \n ${error}\n`); 905 | })); 906 | } 907 | 908 | /** 909 | * Base method for navigation 910 | * 911 | * @deprecated 912 | * @param type 913 | * @param params 914 | */ 915 | oldNavigateTo(type, params) { 916 | console.warn(`Core:\nYou are using deprecated api.\nStarting with api v43 it is recommended to use`, 917 | "%clightning:navigation", 918 | "font-weight: bold", 919 | "for navigation." 920 | ); 921 | const evt = $A.get(type); 922 | evt.setParams(params); 923 | evt.fire(); 924 | } 925 | 926 | /** 927 | * This event enables you to navigate from one Lightning component to another. 928 | * 929 | * @deprecated 930 | * @param componentDef 931 | * @param componentAttributes 932 | */ 933 | oldNavigateToComponent(componentDef, componentAttributes = {}) { 934 | this.oldNavigateTo("e.force:navigateToComponent", { 935 | "componentDef": componentDef, 936 | "componentAttributes": componentAttributes 937 | }); 938 | } 939 | 940 | 941 | /** 942 | * This event enables you to navigate to the list view specified by listViewId. 943 | * 944 | * @deprecated 945 | * @param componentDef 946 | * @param listViewId 947 | * @param listViewName 948 | * @param scope 949 | */ 950 | oldNavigateToList(componentDef, listViewId, listViewName = null, scope) { 951 | this.oldNavigateTo("e.force:navigateToList", { 952 | "listViewId": listViewId, 953 | "listViewName": null, 954 | "scope": scope 955 | }); 956 | } 957 | 958 | /** 959 | * This event enables you to navigate to the object home specified by the scope attribute. 960 | * 961 | * @deprecated 962 | * @param scope 963 | */ 964 | oldNavigateToObjectHome(scope) { 965 | this.oldNavigateTo("e.force:navigateToObjectHome", { 966 | "scope": scope 967 | }) 968 | } 969 | 970 | /** 971 | * This event enables you to navigate to the related list specified by parentRecordId. 972 | * 973 | * @deprecated 974 | * @param relatedListId 975 | * @param parentRecordId 976 | */ 977 | oldNavigateToRelatedList(relatedListId, parentRecordId) { 978 | this.oldNavigateTo("e.force:navigateToRelatedList", { 979 | "relatedListId": relatedListId, 980 | "parentRecordId": parentRecordId 981 | }) 982 | } 983 | 984 | 985 | /** 986 | * This event enables you to navigate to an sObject record specified by recordId. 987 | * 988 | * @deprecated 989 | * @param recordId 990 | * @param slideDevName 991 | */ 992 | oldNavigateToSObject(recordId, slideDevName) { 993 | this.oldNavigateTo("e.force:navigateToSObject", { 994 | "recordId": recordId, 995 | "slideDevName": slideDevName 996 | }) 997 | } 998 | 999 | /** 1000 | * Relative and absolute URLs are supported. Relative URLs are relative to the Salesforce mobile web domain, and retain navigation history. External URLs open in a separate browser window. 1001 | * 1002 | * @param url 1003 | */ 1004 | oldNavigateToUrl(url) { 1005 | this.oldNavigateTo("e.force:navigateToURL", { 1006 | "url": url, 1007 | }) 1008 | } 1009 | } 1010 | 1011 | /** 1012 | * Base page reference without state attribute 1013 | * doc 1014 | */ 1015 | class PageReference { 1016 | 1017 | constructor() { 1018 | this.attributes = {}; 1019 | } 1020 | 1021 | /** 1022 | * Sets a type of a PageReference 1023 | * 1024 | * @param type 1025 | * @returns {PageReference} 1026 | */ 1027 | setType(type) { 1028 | this.type = type; 1029 | return this; 1030 | } 1031 | 1032 | /** 1033 | * Sets attributes for a PageReference 1034 | * 1035 | * @param attributes 1036 | * @returns {PageReference} 1037 | */ 1038 | setAttributes(attributes) { 1039 | this.attributes = attributes; 1040 | return this; 1041 | } 1042 | 1043 | /** 1044 | * Adds an attribute to PageReference 1045 | * 1046 | * @param name 1047 | * @param value 1048 | * @returns {PageReference} 1049 | */ 1050 | addAttribute(name, value) { 1051 | this.attributes[name] = value; 1052 | return this; 1053 | } 1054 | 1055 | /** 1056 | * Removes an attribute from PageReference 1057 | * 1058 | * @param name 1059 | * @returns {PageReference} 1060 | */ 1061 | removeAttribute(name) { 1062 | delete this.attributes[name]; 1063 | return this; 1064 | } 1065 | 1066 | /** 1067 | * Converts object to PageReference 1068 | */ 1069 | toPageReference() { 1070 | const preparedPageReference = {}; 1071 | if (this.type) { 1072 | preparedPageReference.type = this.type; 1073 | } 1074 | preparedPageReference.attributes = this.attributes; 1075 | if (this.state) { 1076 | preparedPageReference.state = this.state; 1077 | } 1078 | return preparedPageReference; 1079 | } 1080 | } 1081 | 1082 | /** 1083 | * Base page reference with state attribute 1084 | * doc 1085 | */ 1086 | class StatefulPageReference extends PageReference { 1087 | 1088 | constructor() { 1089 | super(); 1090 | this.state = {}; 1091 | } 1092 | 1093 | /** 1094 | * Sets a state of the StatefulPageReference 1095 | * 1096 | * @param state 1097 | * @returns {StatefulPageReference} 1098 | */ 1099 | setState(state) { 1100 | this.state = state; 1101 | return this; 1102 | } 1103 | 1104 | /** 1105 | * Adds a state value to StatefulPageReference 1106 | * 1107 | * @param name 1108 | * @param value 1109 | * @returns {StatefulPageReference} 1110 | */ 1111 | addState(name, value) { 1112 | this.state[name] = value; 1113 | return this; 1114 | } 1115 | 1116 | /** 1117 | * Removes a state value from StatefulPageReference 1118 | * 1119 | * @param name 1120 | * @returns {StatefulPageReference} 1121 | */ 1122 | removeState(name) { 1123 | delete this.state[name]; 1124 | return this; 1125 | } 1126 | } 1127 | 1128 | /** 1129 | * A Lightning component that implements the lightning:isUrlAddressable interface, which enables the component to be navigated directly via URL. 1130 | * doc 1131 | */ 1132 | class PageReferenceStandardComponent extends StatefulPageReference { 1133 | 1134 | constructor(componentName) { 1135 | super(); 1136 | this.setType("standard__component"); 1137 | this.addAttribute("componentName", componentName); 1138 | } 1139 | } 1140 | 1141 | /** 1142 | * An authentication for a community. 1143 | * 1144 | */ 1145 | class PageReferenceCommLoginPage extends PageReference { 1146 | 1147 | constructor(action = "login") { 1148 | super(); 1149 | this.setType("comm__loginPage"); 1150 | this.addAttribute("actionName", action); 1151 | } 1152 | 1153 | /** 1154 | * Sets the action to be "login" 1155 | * 1156 | * @returns {PageReference} 1157 | */ 1158 | setActionLogin() { 1159 | return this.addAttribute("actionName", "login"); 1160 | } 1161 | 1162 | /** 1163 | * Sets the action to be "logout" 1164 | * 1165 | * @returns {PageReference} 1166 | */ 1167 | setActionLogout() { 1168 | return this.addAttribute("actionName", "logout"); 1169 | } 1170 | } 1171 | 1172 | /** 1173 | * A page that interacts with a Knowledge Article record. 1174 | * 1175 | */ 1176 | class PageReferenceKnowledgeArticlePage extends PageReference { 1177 | 1178 | constructor() { 1179 | super(); 1180 | this.setType("standard__knowledgeArticlePage"); 1181 | } 1182 | 1183 | /** 1184 | * Sets articleType attribute 1185 | * 1186 | * @param articleType 1187 | * @returns {PageReference} 1188 | */ 1189 | setArticleType(articleType) { 1190 | return this.addAttribute("articleType", articleType); 1191 | } 1192 | 1193 | 1194 | /** 1195 | * Sets urlName attribute 1196 | * 1197 | * @param urlName 1198 | * @returns {PageReference} 1199 | */ 1200 | setUrlName(urlName) { 1201 | return this.addAttribute("urlName", urlName); 1202 | } 1203 | } 1204 | 1205 | /** 1206 | * A standard page with a unique name. If an error occurs, the error view loads and the URL isn’t updated. 1207 | * Types: standard__namedPage, comm__namedPage 1208 | * 1209 | */ 1210 | class PageReferenceNamedPage extends PageReference { 1211 | 1212 | constructor(pageType = "standard__namedPage") { 1213 | super(); 1214 | this.setPageName(pageType); 1215 | } 1216 | 1217 | /** 1218 | * Sets pageName attribute 1219 | * 1220 | * @param pageName 1221 | * @returns {PageReference} 1222 | */ 1223 | setPageName(pageName) { 1224 | return this.addAttribute("pageName", pageName); 1225 | } 1226 | } 1227 | 1228 | /** 1229 | * A page that displays the content mapped to a CustomTab. Visualforce tabs, web tabs, Lightning Pages, and Lightning Component tabs are supported. 1230 | * 1231 | */ 1232 | class PageReferenceNavItemPage extends PageReference { 1233 | 1234 | constructor() { 1235 | super(); 1236 | this.setType("standard__navItemPage"); 1237 | } 1238 | 1239 | /** 1240 | * Sets apiName attribute 1241 | * 1242 | * @param apiName 1243 | * @returns {PageReference} 1244 | */ 1245 | setApiName(apiName) { 1246 | return this.addAttribute("apiName", apiName); 1247 | } 1248 | } 1249 | 1250 | /** 1251 | * A page that interacts with a standard or custom object in the org and supports standard actions for that object. 1252 | * 1253 | */ 1254 | class PageReferenceObjectPage extends StatefulPageReference { 1255 | 1256 | constructor() { 1257 | super(); 1258 | this.setType("standard__objectPage"); 1259 | } 1260 | 1261 | /** 1262 | * Sets objectApiName attribute 1263 | * 1264 | * @param objectApiName 1265 | * @returns {PageReference} 1266 | */ 1267 | setObjectApiName(objectApiName) { 1268 | return this.addAttribute("objectApiName", objectApiName); 1269 | } 1270 | 1271 | /** 1272 | * Sets actionName attribute 1273 | * 1274 | * @param actionName 1275 | * @returns {PageReference} 1276 | */ 1277 | setActionName(actionName) { 1278 | return this.addAttribute("actionName", actionName); 1279 | } 1280 | 1281 | /** 1282 | * Sets filterName attribute 1283 | * 1284 | * @param filterName 1285 | * @returns {StatefulPageReference} 1286 | */ 1287 | setFilterName(filterName) { 1288 | return this.addState("filterName", filterName); 1289 | } 1290 | } 1291 | 1292 | /** 1293 | * A page that interacts with a record in the org and supports standard actions for that record. 1294 | * 1295 | */ 1296 | class PageReferenceRecordPage extends PageReference { 1297 | 1298 | constructor() { 1299 | super(); 1300 | this.setType("standard__recordPage"); 1301 | } 1302 | 1303 | /** 1304 | * Sets actionName attribute 1305 | * 1306 | * @param actionName 1307 | * @returns {PageReference} 1308 | */ 1309 | setActionName(actionName) { 1310 | return this.addAttribute("actionName", actionName); 1311 | } 1312 | 1313 | /** 1314 | * Sets objectApiName attribute 1315 | * 1316 | * @param objectApiName 1317 | * @returns {PageReference} 1318 | */ 1319 | setObjectApiName(objectApiName) { 1320 | return this.addAttribute("objectApiName", objectApiName); 1321 | } 1322 | 1323 | /** 1324 | * Sets recordId attribute 1325 | * 1326 | * @param recordId 1327 | * @returns {PageReference} 1328 | */ 1329 | setRecordId(recordId) { 1330 | return this.addAttribute("recordId", recordId); 1331 | } 1332 | } 1333 | 1334 | /** 1335 | * A page that interacts with a relationship on a particular record in the org. Only related lists are supported. 1336 | * 1337 | */ 1338 | class PageReferenceRecordRelationshipPage extends PageReference { 1339 | 1340 | constructor() { 1341 | super(); 1342 | this.setType("standard__recordRelationshipPage"); 1343 | } 1344 | 1345 | /** 1346 | * Sets recordId attribute 1347 | * 1348 | * @param recordId 1349 | * @returns {PageReference} 1350 | */ 1351 | setRecordId(recordId) { 1352 | return this.addAttribute("recordId", recordId); 1353 | } 1354 | 1355 | /** 1356 | * Sets objectApiName attribute 1357 | * 1358 | * @param objectApiName 1359 | * @returns {PageReference} 1360 | */ 1361 | setObjectApiName(objectApiName) { 1362 | return this.addAttribute("objectApiName", objectApiName); 1363 | } 1364 | 1365 | /** 1366 | * Sets relationshipApiName attribute 1367 | * 1368 | * @param relationshipApiName 1369 | * @returns {PageReference} 1370 | */ 1371 | setRelationshipApiName(relationshipApiName) { 1372 | return this.addAttribute("relationshipApiName", relationshipApiName); 1373 | } 1374 | 1375 | /** 1376 | * Sets actionName attribute 1377 | * 1378 | * @param actionName 1379 | * @returns {PageReference} 1380 | */ 1381 | setActionName(actionName) { 1382 | return this.addAttribute("actionName", actionName); 1383 | } 1384 | } 1385 | 1386 | /** 1387 | * An external URL. 1388 | * 1389 | */ 1390 | class PageReferenceWebPage extends PageReference { 1391 | 1392 | constructor(url) { 1393 | super(); 1394 | this.setType("standard__webPage"); 1395 | this.setUrl(url); 1396 | } 1397 | 1398 | /** 1399 | * Sets url attribute 1400 | * 1401 | * @param url 1402 | * @returns {PageReference} 1403 | */ 1404 | setUrl(url) { 1405 | return this.addAttribute("url", url); 1406 | } 1407 | } 1408 | 1409 | 1410 | /* classes to work with lightning:notificationsLibrary */ 1411 | /** 1412 | * Creates a notice object, based on lightning:notificationsLibrary 1413 | */ 1414 | class Notice extends Library { 1415 | 1416 | /** 1417 | * Creates a new Notice with params 1418 | * 1419 | * @param params 1420 | */ 1421 | constructor(params) { 1422 | super("lightning:notificationsLibrary"); 1423 | this.setParams(params) 1424 | } 1425 | 1426 | /** 1427 | * Sets params for the Notice 1428 | * 1429 | * @param params 1430 | * @returns {Notice} 1431 | */ 1432 | setParams(params = {}) { 1433 | this.params = params; 1434 | return this; 1435 | } 1436 | 1437 | /** 1438 | * Sets header for the Notice 1439 | * 1440 | * @param header 1441 | * @returns {Notice} 1442 | */ 1443 | setHeader(header) { 1444 | this.params.header = header; 1445 | return this; 1446 | } 1447 | 1448 | /** 1449 | * Sets title for the Notice 1450 | * 1451 | * @param title 1452 | * @returns {Notice} 1453 | */ 1454 | setTitle(title) { 1455 | this.params.title = title; 1456 | return this; 1457 | } 1458 | 1459 | /** 1460 | * Sets message for the Notice 1461 | * 1462 | * @param message 1463 | * @returns {Notice} 1464 | */ 1465 | setMessage(message) { 1466 | this.params.message = message; 1467 | return this; 1468 | } 1469 | 1470 | /** 1471 | * Sets variant for the Notice
1472 | * Supported values: 1473 | *
    1474 | *
  • info
  • 1475 | *
  • warning
  • 1476 | *
  • error
  • 1477 | *
1478 | * @param variant 1479 | * @returns {Notice} 1480 | */ 1481 | setVariant(variant) { 1482 | this.params.variant = variant; 1483 | return this; 1484 | } 1485 | 1486 | /** 1487 | * A callback function called, when Notice is closed 1488 | * 1489 | * @param closeCallback 1490 | * @returns {Notice} 1491 | */ 1492 | setCloseCallback(closeCallback) { 1493 | this.params.closeCallback = closeCallback; 1494 | return this; 1495 | } 1496 | 1497 | /** 1498 | * Creates and shows a Notice component.
1499 | * Throws an error if Notice is not supported in this environment.
1500 | * Returns a Promise, if Notice is supported for this environment 1501 | * 1502 | * @returns {*} 1503 | */ 1504 | show() { 1505 | try { 1506 | if (new Environment().isApp()) { 1507 | throw new Error("lightning:notificationsLibrary is not supported in App"); 1508 | } 1509 | return this.library.showNotice(this.params); 1510 | } catch (e) { 1511 | console.error(`Core:\nRunning lightning:notificationsLibrary -> showNotice() raised an exception:\n${e}`) 1512 | } 1513 | } 1514 | } 1515 | 1516 | /** 1517 | * Simple class for creation toasts through lightning:notificationsLibrary.
1518 | * Use this format:
1519 | * 1520 | * new core.ToastX({...}).fire(); 1521 | * 1522 | */ 1523 | class ToastX extends Library { 1524 | 1525 | constructor(params) { 1526 | super("lightning:notificationsLibrary"); 1527 | this.params = params; 1528 | } 1529 | 1530 | /** 1531 | * Sets the type of the toast. Accepted values are:
1532 | *
    1533 | *
  • success
  • 1534 | *
  • warning
  • 1535 | *
  • error
  • 1536 | *
  • info
  • 1537 | *
1538 | * 1539 | * @param type 1540 | * @returns {Toast} 1541 | */ 1542 | setType(type) { 1543 | this.params.type = type; 1544 | return this; 1545 | } 1546 | 1547 | /** 1548 | * Sets the title of the toast. 1549 | * 1550 | * @param title 1551 | * @returns {Toast} 1552 | */ 1553 | setTitle(title) { 1554 | this.params.title = title; 1555 | return this; 1556 | } 1557 | 1558 | /** 1559 | * Sets the message of the toast. 1560 | * 1561 | * @param message 1562 | * @returns {Toast} 1563 | */ 1564 | setMessage(message) { 1565 | this.params.message = message; 1566 | return this; 1567 | } 1568 | 1569 | /** 1570 | * Sets the mode of the toast. 1571 | * Supported values are: 1572 | *
    1573 | *
  • dismissible
  • 1574 | *
  • pester
  • 1575 | *
  • sticky
  • 1576 | *
1577 | * 1578 | * @param mode 1579 | * @returns {Toast} 1580 | */ 1581 | setMode(mode) { 1582 | this.params.mode = mode; 1583 | return this; 1584 | } 1585 | 1586 | /** 1587 | * Sets the duration of the toast. 1588 | * 1589 | * @param duration 1590 | * @returns {Toast} 1591 | */ 1592 | setDuration(duration) { 1593 | this.params.duration = duration; 1594 | return this; 1595 | } 1596 | 1597 | /** 1598 | * Fires a toast
1599 | * Displays an error in console, if toast is not supported in this environment 1600 | */ 1601 | fire() { 1602 | try { 1603 | if (new Environment().isApp()) { 1604 | throw new Error("lightning:notificationsLibrary is not supported in App"); 1605 | } 1606 | this.library.showToast(this.params); 1607 | } catch (e) { 1608 | console.error(`Core:\nRunning lightning:notificationsLibrary -> showToast() raised an exception:\n${e}`) 1609 | } 1610 | } 1611 | } 1612 | 1613 | /** 1614 | * Toast through lightning:notificationsLibrary with predefined mode = "dismissible" and duration = 4s
1615 | * Use this format:
1616 | * 1617 | * new core.ToastXQuick(type, title, message).fire(); 1618 | * 1619 | */ 1620 | class ToastXQuick extends ToastX { 1621 | 1622 | constructor(type, title, message) { 1623 | super({ 1624 | "variant": type, 1625 | "title": title, 1626 | "message": message, 1627 | "mode": "dismissible", 1628 | "duration": 4000 1629 | }); 1630 | } 1631 | } 1632 | 1633 | /** 1634 | * Toast through lightning:notificationsLibrary with predefined mode = "dismissible" and duration = 8s
1635 | * Use this format:
1636 | * 1637 | * new core.ToastXLong(type, title, message).fire(); 1638 | * 1639 | */ 1640 | class ToastXLong extends ToastX { 1641 | 1642 | constructor(type, title, message) { 1643 | super({ 1644 | "type": type, 1645 | "title": title, 1646 | "message": message, 1647 | "mode": "dismissible", 1648 | "duration": 8000 1649 | }); 1650 | } 1651 | } 1652 | 1653 | /** 1654 | * Toast through lightning:notificationsLibrary with predefined 1655 | *
mode = "dismissible" 1656 | *
duration = 4s 1657 | *
type = "success" 1658 | *
title = "Success!" 1659 | *
1660 | * Use this format:
1661 | * 1662 | * new core.ToastXQuickSuccess(message).fire(); 1663 | * 1664 | */ 1665 | class ToastXQuickSuccess extends ToastXQuick { 1666 | 1667 | constructor(message) { 1668 | super( 1669 | "success", 1670 | "Success!", 1671 | message 1672 | ); 1673 | } 1674 | } 1675 | 1676 | /** 1677 | * Toast through lightning:notificationsLibrary with predefined 1678 | *
mode = "dismissible" 1679 | *
duration = 4s 1680 | *
type = "error" 1681 | *
title = "Something went wrong!" 1682 | *
1683 | * Use this format:
1684 | * 1685 | * new core.ToastXQuickError(message).fire(); 1686 | * 1687 | */ 1688 | class ToastXQuickError extends ToastXQuick { 1689 | 1690 | constructor(message) { 1691 | super( 1692 | "error", 1693 | "Something went wrong!", 1694 | message 1695 | ); 1696 | } 1697 | } 1698 | 1699 | /** 1700 | * Toast through lightning:notificationsLibrary with predefined 1701 | *
mode = "dismissible" 1702 | *
duration = 8s 1703 | *
type = "success" 1704 | *
title = "Success!" 1705 | *
1706 | * Use this format:
1707 | * 1708 | * new core.ToastXLongSuccess(message).fire(); 1709 | * 1710 | */ 1711 | class ToastXLongSuccess extends ToastXLong { 1712 | 1713 | constructor(message) { 1714 | super( 1715 | "success", 1716 | "Success!", 1717 | message 1718 | ); 1719 | } 1720 | } 1721 | 1722 | /** 1723 | * Toast through lightning:notificationsLibrary with predefined 1724 | *
mode = "dismissible" 1725 | *
duration = 8s 1726 | *
type = "error" 1727 | *
title = "Something went wrong!" 1728 | *
1729 | * Use this format:
1730 | * 1731 | * new core.ToastXLongError(message).fire(); 1732 | * 1733 | */ 1734 | class ToastXLongError extends ToastXLong { 1735 | 1736 | constructor(message) { 1737 | super( 1738 | "error", 1739 | "Something went wrong!", 1740 | message 1741 | ); 1742 | } 1743 | } 1744 | 1745 | 1746 | /* classes to work with lightning:overlayLibrary */ 1747 | /** 1748 | * Creates a Modal with lightning:overlayLibrary 1749 | */ 1750 | class Modal extends Library { 1751 | 1752 | /** 1753 | * Creates a new Modal with body and Footer 1754 | * 1755 | * @param body 1756 | * @param footer 1757 | */ 1758 | constructor(body = null, footer = null) { 1759 | super("lightning:overlayLibrary"); 1760 | this.setBody(body); 1761 | this.setFooter(footer); 1762 | } 1763 | 1764 | /** 1765 | * Sets the body for the Modal 1766 | * 1767 | * @param body 1768 | * @returns {Modal} 1769 | */ 1770 | setBody(body = null) { 1771 | this._checkAttribute(body); 1772 | this.body = body; 1773 | return this; 1774 | } 1775 | 1776 | /** 1777 | * Sets footer for the Modal 1778 | * 1779 | * @param footer 1780 | * @returns {Modal} 1781 | */ 1782 | setFooter(footer = null) { 1783 | this._checkAttribute(footer); 1784 | this.footer = footer; 1785 | return this; 1786 | } 1787 | 1788 | /** 1789 | * Sets header for the Modal 1790 | * 1791 | * @param header 1792 | * @returns {Modal} 1793 | */ 1794 | setHeader(header = "") { 1795 | this.header = header; 1796 | return this; 1797 | } 1798 | 1799 | /** 1800 | * Sets show close button for the Modal 1801 | * 1802 | * @param showCloseButton 1803 | * @returns {Modal} 1804 | */ 1805 | setShowCloseButton(showCloseButton = true) { 1806 | this.showCloseButton = showCloseButton; 1807 | return this; 1808 | } 1809 | 1810 | /** 1811 | * Sets css class for the Modal 1812 | * 1813 | * @param cssClass 1814 | * @returns {Modal} 1815 | */ 1816 | setCssClass(cssClass = "") { 1817 | this.cssClass = cssClass; 1818 | return this; 1819 | } 1820 | 1821 | /** 1822 | * Sets close callback for the Modal 1823 | * 1824 | * @param closeCallback 1825 | * @returns {Modal} 1826 | */ 1827 | setCloseCallback(closeCallback) { 1828 | this.closeCallback = closeCallback; 1829 | return this; 1830 | } 1831 | 1832 | /** 1833 | * Builds component for Body and Footer and builds a Modal from them 1834 | * 1835 | * @returns {LightningPromise} 1836 | */ 1837 | show() { 1838 | try { 1839 | if (new Environment().isApp()) { 1840 | throw new Error(`Core:\nlightning:overlayLibrary is not supported in App`); 1841 | } 1842 | const components = new Components(); 1843 | if (this.body !== null) { 1844 | components.addComponent(this.body); 1845 | } 1846 | if (this.footer !== null) { 1847 | components.addComponent(this.footer); 1848 | } 1849 | 1850 | return new LightningPromise((resolve, reject) => { 1851 | components.create() 1852 | .then((components) => { 1853 | let body = null; 1854 | let footer = null; 1855 | if (components.length >= 1) { 1856 | body = components[0]; 1857 | } 1858 | if (components.length === 2) { 1859 | footer = components[1]; 1860 | } 1861 | try { 1862 | resolve(this.library.showCustomModal(this.toParams(body, footer))); 1863 | } catch (e) { 1864 | console.error(`Core:\nRunning lightning:overlayLibrary -> showCustomModal() raised an exception:\n${e}`) 1865 | reject(e) 1866 | } 1867 | }) 1868 | .catch((error) => { 1869 | console.error(`Core:\nCreating component(s) for modal raised an exception:\n${error}`); 1870 | reject(error) 1871 | }); 1872 | }); 1873 | } catch (e) { 1874 | console.error(`Core:\nRunning lightning:overlayLibrary -> Modal.show() raised an exception:\n${e}`) 1875 | } 1876 | } 1877 | 1878 | /** 1879 | * Converts the Modal class to params, which are applicable to create a new Modal with lightning:overlayLibrary 1880 | * 1881 | * @param body 1882 | * @param footer 1883 | * @returns {{header: string, body: *, footer: *, showCloseButton: boolean, cssClass: string, closeCallback: *}} 1884 | */ 1885 | toParams(body, footer) { 1886 | return { 1887 | header: this.header, 1888 | body: body, 1889 | footer: footer, 1890 | showCloseButton: this.showCloseButton, 1891 | cssClass: this.cssClass, 1892 | closeCallback: this.closeCallback 1893 | } 1894 | } 1895 | 1896 | _checkAttribute(component) { 1897 | if (component && !(component instanceof Component)) { 1898 | const message = `Core:\nArgument must be of type core.Component.\n`; 1899 | console.error(message); 1900 | throw new Error(message); 1901 | } 1902 | } 1903 | } 1904 | 1905 | /** 1906 | * Creates a Popover with lightning:overlayLibrary 1907 | */ 1908 | class Popover extends Library { 1909 | 1910 | constructor(body) { 1911 | super("lightning:overlayLibrary"); 1912 | this.setBody(body); 1913 | } 1914 | 1915 | /** 1916 | * Sets the body for the Popover 1917 | * 1918 | * @param body 1919 | */ 1920 | setBody(body) { 1921 | this.body = body; 1922 | return this; 1923 | } 1924 | 1925 | /** 1926 | * Sets reference selector for the Popover 1927 | * 1928 | * @param referenceElementSelector 1929 | * @returns {Popover} 1930 | */ 1931 | setReferenceSelector(referenceElementSelector) { 1932 | this.referenceElementSelector = referenceElementSelector; 1933 | return this; 1934 | } 1935 | 1936 | /** 1937 | * Sets reference selector for the Popover 1938 | * 1939 | * @param cssClassList 1940 | * @returns {Popover} 1941 | */ 1942 | setCssClass(cssClassList) { 1943 | this.cssClassList = cssClassList; 1944 | return this; 1945 | } 1946 | 1947 | /** 1948 | * Creates a Popover and shows it in UI 1949 | * 1950 | * @returns {LightningPromise} 1951 | */ 1952 | show() { 1953 | try { 1954 | if (new Environment().isApp()) { 1955 | throw new Error(`Core:\nlightning:overlayLibrary is not supported in App`); 1956 | } 1957 | 1958 | return this.library.showCustomPopover(this.toParams()); 1959 | } catch (e) { 1960 | console.error(`Core:\nRunning lightning:overlayLibrary -> Popover.show() raised an exception:\n${e}`) 1961 | } 1962 | } 1963 | 1964 | /** 1965 | * Converts the Popover class to params, which are applicable to create a new Popover with lightning:overlayLibrary 1966 | * 1967 | * @returns {{body: (*|string), referenceSelector: *, cssClass: *}} 1968 | */ 1969 | toParams() { 1970 | return { 1971 | body: this.body || "", 1972 | referenceSelector: this.referenceElementSelector, 1973 | cssClass: this.cssClassList 1974 | }; 1975 | } 1976 | } 1977 | 1978 | 1979 | /* classes to work with browser storage */ 1980 | /** 1981 | * Base class for the Local- and Session Storage 1982 | * @see LocalStorage 1983 | * @see SessionStorage 1984 | */ 1985 | class Storage { 1986 | 1987 | constructor(sourceName) { 1988 | this.domain = new URL(window.location).origin; 1989 | this.source = window[sourceName]; 1990 | } 1991 | 1992 | /** 1993 | * Returns domain name for current Storage 1994 | * 1995 | * @returns {string | *} 1996 | */ 1997 | getDomain() { 1998 | return this.domain; 1999 | } 2000 | 2001 | /** 2002 | * Returns a named items from the Storage 2003 | * 2004 | * @param name 2005 | * @returns {Object|SVGPoint|SVGTransform|SVGNumber|string|T|SVGLength|SVGPathSeg} 2006 | */ 2007 | get(name) { 2008 | return this.source.getItem(name); 2009 | } 2010 | 2011 | /** 2012 | * REturns a named object from the Storage 2013 | * 2014 | * @param name 2015 | * @returns {any} 2016 | */ 2017 | getObject(name) { 2018 | return JSON.parse(this.get(name)); 2019 | } 2020 | 2021 | /** 2022 | * Returns all the items from the Storage 2023 | * 2024 | * @returns {*} 2025 | */ 2026 | getAll() { 2027 | return this.source; 2028 | } 2029 | 2030 | /** 2031 | * Sets a named item into the Storage 2032 | * 2033 | * @param name 2034 | * @param value 2035 | */ 2036 | set(name, value) { 2037 | this.source.setItem(name, value); 2038 | } 2039 | 2040 | /** 2041 | * Sets a named object into the Storage 2042 | * 2043 | * @param name 2044 | * @param value 2045 | */ 2046 | setObject(name, value) { 2047 | this.source.setItem(name, JSON.stringify(value)); 2048 | } 2049 | 2050 | /** 2051 | * Checks if there's a named item in the Storage 2052 | * 2053 | * @param name 2054 | * @returns {boolean} 2055 | */ 2056 | has(name) { 2057 | return !$A.util.isUndefinedOrNull(this.get(name)); 2058 | } 2059 | 2060 | /** 2061 | * Removes a named items from the Storage 2062 | * 2063 | * @param name 2064 | */ 2065 | remove(name) { 2066 | delete this.source[name]; 2067 | } 2068 | 2069 | /** 2070 | * Removes all named items from the Storage 2071 | */ 2072 | clear() { 2073 | this.source.clear(); 2074 | } 2075 | 2076 | /** 2077 | * Prints everything from the Storage 2078 | */ 2079 | print() { 2080 | console.log(`Storage for ${this.getDomain()}`); 2081 | const keys = Object.keys(localStorage); 2082 | for (let key of keys) { 2083 | console.log(`${key}: ${this.get(key)}`); 2084 | } 2085 | } 2086 | } 2087 | 2088 | /** 2089 | * Storage class, which represents a Local Storage 2090 | */ 2091 | class LocalStorage extends Storage { 2092 | 2093 | constructor() { 2094 | super("localStorage"); 2095 | } 2096 | } 2097 | 2098 | /** 2099 | * Storage class, which represents a Session Storage 2100 | */ 2101 | class SessionStorage extends Storage { 2102 | 2103 | constructor() { 2104 | super("sessionStorage") 2105 | } 2106 | } 2107 | 2108 | const core = { 2109 | "File": File, 2110 | 2111 | "Toast": Toast, 2112 | "ToastQuick": ToastQuick, 2113 | "ToastLong": ToastLong, 2114 | "ToastQuickSuccess": ToastQuickSuccess, 2115 | "ToastQuickError": ToastQuickError, 2116 | "ToastLongSuccess": ToastLongSuccess, 2117 | "ToastLongError": ToastLongError, 2118 | 2119 | "Notice": Notice, 2120 | "ToastX": ToastX, 2121 | "ToastXQuick": ToastXQuick, 2122 | "ToastXLong": ToastXLong, 2123 | "ToastXQuickSuccess": ToastXQuickSuccess, 2124 | "ToastXQuickError": ToastXQuickError, 2125 | "ToastXLongSuccess": ToastXLongSuccess, 2126 | "ToastXLongError": ToastXLongError, 2127 | 2128 | 2129 | "ServerAction": ServerAction, 2130 | "ServerActionHandled": ServerActionHandled, 2131 | "ServerActionPromise": ServerActionPromise, 2132 | "ServerActionPromiseHandled": ServerActionPromiseHandled, 2133 | "LightningPromise": LightningPromise, 2134 | 2135 | 2136 | "Component": Component, 2137 | "Components": Components, 2138 | 2139 | 2140 | "Navigation": Navigation, 2141 | "PageReferenceStandardComponent": PageReferenceStandardComponent, 2142 | "PageReferenceCommLoginPage": PageReferenceCommLoginPage, 2143 | "PageReferenceKnowledgeArticlePage": PageReferenceKnowledgeArticlePage, 2144 | "PageReferenceNamedPage": PageReferenceNamedPage, 2145 | "PageReferenceNavItemPage": PageReferenceNavItemPage, 2146 | "PageReferenceObjectPage": PageReferenceObjectPage, 2147 | "PageReferenceRecordPage": PageReferenceRecordPage, 2148 | "PageReferenceRecordRelationshipPage": PageReferenceRecordRelationshipPage, 2149 | "PageReferenceWebPage": PageReferenceWebPage, 2150 | 2151 | 2152 | "Modal": Modal, 2153 | "Popover": Popover, 2154 | 2155 | 2156 | "LocalStorage": LocalStorage, 2157 | "SessionStorage": SessionStorage 2158 | }; 2159 | 2160 | return ((component) => { 2161 | 2162 | const loadModules = (component, core) => { 2163 | const body = component.get("v.body"); 2164 | body.filter(component => { 2165 | return component.isInstanceOf("c:lightningCoreModule"); 2166 | }) 2167 | .forEach(module => { 2168 | let exported = null; 2169 | try { 2170 | exported = module.export() 2171 | } catch (e) { 2172 | console.error(`Core:\nModule ${module.getName()} does not implement export method\n${e}`); 2173 | return; 2174 | } 2175 | if (!exported || !Array.isArray(exported) || exported.length !== 2 || typeof exported[0] !== 'string') { 2176 | console.error(`Core:\nModule ${module.getName()} has wrong return format.\nReturn format should be [module_name, {exported_classes}]`); 2177 | return; 2178 | } 2179 | if (core[exported[0]]) { 2180 | console.error(`Core:\n${exported[0]} namespace from module ${module.getName()} is already defined in lightning-core. Please, consider other module name`); 2181 | return; 2182 | } 2183 | core[exported[0]] = exported[1]; 2184 | }); 2185 | }; 2186 | 2187 | return { 2188 | init: () => { 2189 | if ($A.util.isUndefinedOrNull(window.core)) { 2190 | Object.defineProperty(window, "core", { 2191 | writable: false, 2192 | configurable: false, 2193 | enumerable: false, 2194 | value: core 2195 | }); 2196 | loadModules(component, core); 2197 | } 2198 | } 2199 | }; 2200 | })(component); 2201 | 2202 | } 2203 | }) 2204 | -------------------------------------------------------------------------------- /src/aura/lightningCoreModule/lightningCoreModule.intf: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/aura/lightningCoreModule/lightningCoreModule.intf-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 46.0 4 | lightningCoreModule 5 | 6 | --------------------------------------------------------------------------------