Expand my Community achievements bar.

Cannot use SCF.addComponent manually

Avatar

Level 4

Hey everyone,

I'm working on some pretty heavy SCF extensions and have come across an error that looks like it's code that was refactored incompletely. When the SCF loads, the fullBootstrap method is called, which in turn adds the views and models to each component using the two functions addView and addModel. This works fine when the page is rendered normally. I am trying to dynamically inject components into modals when elements are clicked. Due to the specific nature of this problem, I am trying to write my own code to properly introduce a new component to the SCF ecosystem. After some digging through source code, I came across this window-bound function:

SCF.addComponent = function(el) { var $el = $(el); if ($el.length === 0) { throw "Could not find requested element on page."; } var component = extractComponentFromElement($el); if (component === null) { throw "Component is already loaded."; } if (!component.id) { throw "Component does not have a data-component-id attribute, which is required"; } if (!component.type) { throw "Component does not have a data-scf-component attribute, which is required."; } return createComponent(component.id, component.type, component.template, component.el); };

 

So great! I can use this to add my component if I load the resource directly as HTML. It works fine, but there is an error occurring due to a variable being undefined. I traced the problem to this helper method:

var createComponent = function(id, type, template, $el) { // Find the json model on the page. var modelHolder = $CQ("script[type='application/json'][id='" + id + "']"); var component = { id: id, type: type, template: template, modelHolder: modelHolder, el: $el }; // model is the undefined variable var model = addModel(component); var view = addView(component); return SCF.addLoadedComponent(type, model, view); };

Basically, when model and view are passed into SCF.addLoadedComponent, they are both undefined - and rightfully so - both addModel and addView have no return statements. They both simply modify the component being passed in. In other instances, addView and addModel are called, and then the component.type and component.model are passed in to the next method, as so:

SCF.addLoadedComponent(component.type, component.model, component.view);

I believe this should also be the case in createComponent. I guess this is code that simply isn't used, but there's no theoretical way for model and view to be defined here. I believe this to be an issue during a refactor process that was overlooked. The problem is, due to the design of the code, this method is quite a beast to override and implement myself. I would like to get some input on this before I proceed. Am I missing something? Is there a hotfix for this?

3 Replies

Avatar

Level 4

To follow up here, if I attempt to pass in component.model and component.view directly, the entire page breaks (like, browser crashes). However, if I simply return the model and view in addView and addModel, the error is gone and all works as expected.

Avatar

Level 1

To dynamically add new components to a page (after it was bootstrapped) or to lazy load components, you can follow the document here: https://docs.adobe.com/docs/en/aem/6-2/develop/communities/essentials/sideloading.html#Dynamic%20Loa...

For your use case, of injecting some component on a modal dialog launch, you could do something like this:
 

var launchDialog = function(model) { var el = document.createElement("div"); // initialize your view object with the loaded model var myView = new MyView({ "model": model, "el": el }); //load the template that you want to use to render your view with //findTemplate can load based on a resource path, template/selector name or resourceType var template = SCF.findTemplate(model.id, templateName, resourceType); myView.template = template; //renders the view into the element myView.render(); //fictitious method that opens a dialog and add the rendered element to the dialog content openDialog({ content: myView.el, header: CQ.I18n.get("Content Details") }); }; // find loads the model JSON from the server or from a local cache // launchDialog is called after "success"ful model load var myModel = MyModel.find(modelId, launchDialog, true); //MyModel and MyView are your custom view and model

Hope this helps!

Avatar

Level 4

Thanks for your answer,

The documentation you linked states that I should be using SCF.addComponent, which is the exact situation I'm describing above. My primary concern here is that there's literally a line of code that says:

var model = addModel(component);

where addModel does not return a value. Clearly this is a bug, and I should in fact be using SCF.addComponent, especially when the element has been lazy loaded as an HTML resource, so it has the data-component-id and data-scf-component already. Manually defining the model type, binding the template, and calling render seems much less reasonable than calling a single method with an element that is already prepared as HTML by the servlet.

I should be able to simply do the following, assuming I am dynamically populating Bootstrap Modals when elements are clicked that have a data-src attribute containing the content's path:

$CQ('#event-modal').modal('show').load($CQ(this).attr('data-src') + ".html", function () { SCF.addComponent($CQ('#event-modal').children('.scf')); });