1/** 2 * Copyright © Magento, Inc. All rights reserved. 3 * See COPYING.txt for license details. 4 */ 5 6define([ 7 'underscore', 8 'jquery', 9 'mageUtils', 10 'uiRegistry', 11 './types', 12 '../../lib/logger/console-logger' 13], function (_, $, utils, registry, types, consoleLogger) { 14 'use strict'; 15 16 var templates = registry.create(), 17 layout = {}, 18 cachedConfig = {}; 19 20 /** 21 * Build name from parent name and node name 22 * 23 * @param {Object} parent 24 * @param {Object} node 25 * @param {String} [name] 26 * @returns {String} 27 */ 28 function getNodeName(parent, node, name) { 29 var parentName = parent && parent.name; 30 31 if (typeof name !== 'string') { 32 name = node.name || name; 33 } 34 35 return utils.fullPath(parentName, name); 36 } 37 38 /** 39 * Get node type from node or parent. 40 * 41 * @param {Object} parent 42 * @param {Object} node 43 * @returns {String} 44 */ 45 function getNodeType(parent, node) { 46 return node.type || parent && parent.childType; 47 } 48 49 /** 50 * Get data scope based on parent data scope and node data scope. 51 * 52 * @param {Object} parent 53 * @param {Object} node 54 * @returns {String} 55 */ 56 function getDataScope(parent, node) { 57 var dataScope = node.dataScope, 58 parentScope = parent && parent.dataScope; 59 60 return !utils.isEmpty(parentScope) ? 61 !utils.isEmpty(dataScope) ? 62 parentScope + '.' + dataScope : 63 parentScope : 64 dataScope || ''; 65 } 66 67 /** 68 * Load node dependencies on other instances. 69 * 70 * @param {Object} node 71 * @returns {jQueryPromise} 72 */ 73 function loadDeps(node) { 74 var loaded = $.Deferred(), 75 loggerUtils = consoleLogger.utils; 76 77 if (node.deps) { 78 consoleLogger.utils.asyncLog( 79 loaded, 80 { 81 data: { 82 component: node.name, 83 deps: node.deps 84 }, 85 messages: loggerUtils.createMessages( 86 'depsStartRequesting', 87 'depsFinishRequesting', 88 'depsLoadingFail' 89 ) 90 } 91 ); 92 } 93 94 registry.get(node.deps, function (deps) { 95 node.provider = node.extendProvider ? deps && deps.name : node.provider; 96 loaded.resolve(node); 97 }); 98 99 return loaded.promise(); 100 } 101 102 /** 103 * Load node component file via requirejs. 104 * 105 * @param {Object} node 106 * @returns {jQueryPromise} 107 */ 108 function loadSource(node) { 109 var loaded = $.Deferred(), 110 source = node.component; 111 112 consoleLogger.info('componentStartLoading', { 113 component: node.component 114 }); 115 116 require([source], function (constr) { 117 consoleLogger.info('componentFinishLoading', { 118 component: node.component 119 }); 120 loaded.resolve(node, constr); 121 }, function (err) { 122 consoleLogger.error('componentLoadingFail', { 123 component: node.component, 124 errorMsg: err 125 }); 126 }); 127 128 return loaded.promise(); 129 } 130 131 /** 132 * Create a new component instance and set it to the registry. 133 * 134 * @param {Object} node 135 * @param {Function} Constr 136 */ 137 function initComponent(node, Constr) { 138 var component = new Constr(_.omit(node, 'children')); 139 140 consoleLogger.info('componentStartInitialization', { 141 component: node.component, 142 componentName: node.name 143 }); 144 145 registry.set(node.name, component); 146 } 147 148 /** 149 * Application entry point. 150 * 151 * @param {Object} nodes 152 * @param {Object} parent 153 * @param {Boolean} cached 154 * @param {Boolean} merge 155 * @returns {Boolean|undefined} 156 */ 157 function run(nodes, parent, cached, merge) { 158 if (_.isBoolean(merge) && merge) { 159 layout.merge(nodes); 160 161 return false;
162 } 163 164 if (cached) { 165 cachedConfig[_.keys(nodes)[0]] = JSON.parse(JSON.stringify(nodes)); 166 } 167 168 _.each(nodes || [], layout.iterator.bind(layout, parent)); 169 } 170 171 _.extend(layout, { 172 /** 173 * Determines if node ready to be added or process it. 174 * 175 * @param {Object} parent 176 * @param {Object|String} node 177 */ 178 iterator: function (parent, node) { 179 var action = _.isString(node) ? 180 this.addChild : 181 this.process; 182 183 action.apply(this, arguments); 184 }, 185 186 /** 187 * Prepare component. 188 * 189 * @param {Object} parent 190 * @param {Object} node 191 * @param {String} name 192 * @returns {Object} 193 */ 194 process: function (parent, node, name) { 195 if (!parent && node.parent) { 196 return this.waitParent(node, name); 197 } 198 199 if (node.nodeTemplate) { 200 return this.waitTemplate.apply(this, arguments); 201 } 202 203 node = this.build.apply(this, arguments); 204 205 if (!registry.has(node.name)) { 206 this.addChild(parent, node) 207 .manipulate(node) 208 .initComponent(node); 209 } 210 211 if (node) { 212 run(node.children, node); 213 } 214 215 return this; 216 }, 217 218 /** 219 * Detailed processing of component config. 220 * 221 * @param {Object} parent 222 * @param {Object} node 223 * @param {String} name 224 * @returns {Boolean|Object} 225 */ 226 build: function (parent, node, name) { 227 var defaults = parent && parent.childDefaults || {}, 228 children = this.filterDisabledChildren(node.children), 229 type = getNodeType(parent, node), 230 dataScope = getDataScope(parent, node), 231 component, 232 extendDeps = true, 233 nodeName; 234 235 node.children = false; 236 node.extendProvider = true; 237 238 if (node.config && node.config.provider || node.provider) { 239 node.extendProvider = false;
240 } 241 242 if (node.config && node.config.deps || node.deps) { 243 extendDeps = false; 244 } 245 246 node = utils.extend({ 247 }, types.get(type), defaults, node); 248 249 nodeName = getNodeName(parent, node, name); 250 251 if (registry.has(nodeName)) { 252 component = registry.get(nodeName); 253 component.children = children; 254 255 return component; 256 } 257 258 if (extendDeps && parent && parent.deps && type) { 259 node.deps = parent.deps; 260 } 261 262 _.extend(node, node.config || {}, { 263 index: node.name || name, 264 name: nodeName, 265 dataScope: dataScope, 266 parentName: utils.getPart(nodeName, -2), 267 parentScope: utils.getPart(dataScope, -2) 268 }); 269 270 node.children = children; 271 node.componentType = node.type; 272 273 delete node.type; 274 delete node.config; 275 276 if (children) { 277 node.initChildCount = _.size(children); 278 } 279 280 if (node.isTemplate) { 281 node.isTemplate = false; 282 283 templates.set(node.name, node); 284 registry.get(node.parentName, function (parentComp) { 285 parentComp.childTemplate = node; 286 }); 287 288 return false; 289 } 290 291 if (node.componentDisabled === true) { 292 return false; 293 } 294 295 return node; 296 }, 297 298 /** 299 * Filter out all disabled components. 300 * 301 * @param {Object} children 302 * @returns {*} 303 */ 304 filterDisabledChildren: function (children) { 305 var cIds; 306 307 //cleanup children config.componentDisabled = true 308 if (children && typeof children === 'object') { 309 cIds = Object.keys(children); 310 311 if (cIds) { 312 _.each(cIds, function (cId) { 313 if (typeof children[cId] === 'object' && 314 children[cId].hasOwnProperty('config') && 315 typeof children[cId].config === 'object' && 316 children[cId].config.hasOwnProperty('componentDisabled') && 317 children[cId].config.componentDisabled === true) { 318 delete children[cId]; 319 } 320 }); 321 } 322 } 323 324 return children; 325 }, 326 327 /** 328 * Init component. 329 * 330 * @param {Object} node 331 * @returns {Object} 332 */ 333 initComponent: function (node) { 334 if (!node.component) { 335 return this; 336 } 337 338 loadDeps(node) 339 .then(loadSource) 340 .done(initComponent); 341 342 return this; 343 } 344 }); 345 346 _.extend(layout, { 347 /** 348 * Loading component marked as isTemplate. 349 * 350 * @param {Object} parent 351 * @param {Object} node 352 * @returns {Object} 353 */ 354 waitTemplate: function (parent, node) { 355 var args = _.toArray(arguments); 356 357 templates.get(node.nodeTemplate, function () { 358 this.applyTemplate.apply(this, args); 359 }.bind(this)); 360 361 return this; 362 }, 363 364 /** 365 * Waiting for parent component and process provided component. 366 * 367 * @param {Object} node 368 * @param {String} name 369 * @returns {Object} 370 */ 371 waitParent: function (node, name) { 372 var process = this.process.bind(this); 373 374 registry.get(node.parent, function (parent) { 375 process(parent, node, name); 376 }); 377 378 return this; 379 }, 380 381 /** 382 * Processing component marked as isTemplate. 383 * 384 * @param {Object} parent 385 * @param {Object} node 386 * @param {String} name 387 */ 388 applyTemplate: function (parent, node, name) { 389 var template = templates.get(node.nodeTemplate); 390 391 node = utils.extend({}, template, node); 392 393 delete node.nodeTemplate; 394 395 this.process(parent, node, name); 396 } 397 }); 398 399 _.extend(layout, { 400 /** 401 * Determines inserting strategy. 402 * 403 * @param {Object} node 404 * @returns {Object} 405 */ 406 manipulate: function (node) { 407 var name = node.name; 408 409 if (node.appendTo) { 410 this.insert(name, node.appendTo, -1); 411 } 412 413 if (node.prependTo) { 414 this.insert(name, node.prependTo, 0); 415 } 416 417 if (node.insertTo) { 418 this.insertTo(name, node.insertTo); 419 } 420 421 return this; 422 }, 423 424 /** 425 * Insert component to provide target and position. 426 * 427 * @param {Object|String} item 428 * @param {Object} target 429 * @param {Number} position 430 * @returns {Object} 431 */ 432 insert: function (item, target, position) { 433 registry.get(target, function (container) { 434 container.insertChild(item, position); 435 }); 436 437 return this; 438 }, 439 440 /** 441 * Insert component into multiple targets. 442 * 443 * @param {Object} item 444 * @param {Array} targets 445 * @returns {Object} 446 */ 447 insertTo: function (item, targets) { 448 _.each(targets, function (info, target) { 449 this.insert(item, target, info.position); 450 }, this); 451 452 return this; 453 }, 454 455 /** 456 * Add provided child to parent. 457 * 458 * @param {Object} parent 459 * @param {Object|String} child 460 * @returns {Object} 461 */ 462 addChild: function (parent, child) { 463 var name; 464 465 if (parent && parent.component) { 466 name = child.name || child; 467 468 this.insert(name, parent.name, child.sortOrder); 469 } 470 471 return this; 472 }, 473 474 /**
475 * Merge components configuration with cached configuration. 476 * 477 * @param {Array} components 478 */ 479 merge: function (components) { 480 var cachedKey = _.keys(components)[0], 481 compared = utils.compare(cachedConfig[cachedKey], components), 482 remove = this.filterComponents(this.getByProperty(compared.changes, 'type', 'remove'), true), 483 update = this.getByProperty(compared.changes, 'type', 'update'), 484 dataSources = this.getDataSources(components), 485 names, index, name, component; 486 487 _.each(dataSources, function (val, key) { 488 name = key.replace(/\.children|\.config/g, ''); 489 component = registry.get(name); 490 491 component.cacheData(); 492 component.updateConfig( 493 true, 494 this.getFullConfig(key, components), 495 this.getFullConfig(key, cachedConfig[cachedKey]) 496 ); 497 }, this); 498 499 _.each(remove, function (val) { 500 component = registry.get(val.path); 501 502 if (component) { 503 component.destroy(); 504 } 505 }); 506 507 update = _.compact(_.filter(update, function (val) { 508 return !_.isEqual(val.oldValue, val.value); 509 })); 510 511 _.each(update, function (val) { 512 names = val.path.split('.'); 513 index = Math.max(_.lastIndexOf(names, 'config'), _.lastIndexOf(names, 'children') + 2); 514 name = _.without(names.splice(0, index), 'children', 'config').join('.'); 515 component = registry.get(name); 516 517 if (val.name === 'sortOrder' && component) { 518 registry.get(component.parentName).insertChild(component, val.value); 519 } else if (component) { 520 component.updateConfig( 521 val.oldValue, 522 val.value, 523 val.path 524 ); 525 } 526 }, this); 527 528 run(components, undefined, true); 529 }, 530 531 /** 532 * Recursive dataSource assignment. 533 * 534 * @param {Object} config 535 * @param {String} parentPath 536 * @returns {Object} 537 */ 538 getDataSources: function (config, parentPath) { 539 var dataSources = {}, 540 key, obj; 541 542 /* eslint-disable no-loop-func, max-depth */ 543 for (key in config) { 544 if (config.hasOwnProperty(key)) { 545 if ( 546 key === 'type' && 547 config[key] === 'dataSource' && 548 config.hasOwnProperty('config') 549 ) { 550 dataSources[parentPath + '.config'] = config.config; 551 } else if (_.isObject(config[key])) { 552 obj = this.getDataSources(config[key], utils.fullPath(parentPath, key)); 553 554 _.each(obj, function (value, path) { 555 dataSources[path] = value; 556 }); 557 } 558 } 559 } 560 561 /* eslint-enable no-loop-func, max-depth */ 562 563 return dataSources; 564 }, 565 566 /** 567 * Configuration getter. 568 * 569 * @param {String} path 570 * @param {Object} config 571 * @returns {Boolean|Object} 572 */ 573 getFullConfig: function (path, config) { 574 var index; 575 576 path = path.split('.'); 577 index = _.lastIndexOf(path, 'config'); 578 579 if (!~index) { 580 return false; 581 } 582 path = path.splice(0, index); 583 584 _.each(path, function (val) { 585 config = config[val]; 586 }); 587 588 return config.config; 589 }, 590 591 /** 592 * Filter data by property and value. 593 * 594 * @param {Object} data 595 * @param {String} prop 596 * @param {*} propValue 597 */ 598 getByProperty: function (data, prop, propValue) { 599 return _.filter(data, function (value) { 600 return value[prop] === propValue; 601 }); 602 }, 603 604 /** 605 * Filter components. 606 * 607 * @param {Array} data 608 * @param {Boolean} splitPath 609 * @param {Number} index 610 * @param {String} separator 611 * @param {String} keyName 612 * @returns {Array} 613 */ 614 filterComponents: function (data, splitPath, index, separator, keyName) { 615 var result = [], 616 names, length; 617 618 index = -2; 619 separator = '.' || separator; 620 keyName = 'children' || keyName; 621 622 _.each(data, function (val) { 623 names = val.path.split(separator); 624 length = names.length; 625 626 if (names[length + index] === keyName) { 627 val.path = splitPath ? _.without(names, keyName).join(separator) : val.path; 628 result.push(val); 629 } 630 }); 631 632 return result; 633 } 634 }); 635 636 return run; 637});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.