How to depend on private modules?

Hello 👋

I've been building my Smart UI extension and I've arrived at a point, where I want to extend a model from the /csui module that's not in the bundle index.

That means it all works @localhost, but grunt fails at requirejsDependencyCheck with the common "Error: At least one module depends on a private module from other component."

The obvious solution to me is just to go to the csui bundles and add the missing bundle line. "But that can't be correct, can it? Would that even work after deployment?" I thought.

So my question is: What's the correct way to depend on private modules?

Thanks and have a nice day,

Jan

Tagged:

Comments

  • By the way, I'm on version 21.3 and the thing I want to extend is "csui/models/widget/myassignments.model".

    I've dealt with a similar problem in the past by completely duplicating the private file, just to change a line or two, but that introduced more problems than it was worth in the long run.

    So I'm trying to figure out a way to do this right, but after spending more time on it than I should have, and being unable to find anyone with the same problem, I thought I might as well be the one to ask.

    Sorry if it's something obvious. 😅

  • Hello again,

    No one had a similar problem?

    It would really help me to know what's the correct way to deal with this situation.

  • Hi Jan,

    Firstly, I'd ask if you can summarise your goal....what is the end outcome you're trying to achieve?

    Unless the item you're needing to work with is publicly exposed via an extension point, I'm unaware of any means to extend a private object.

    Na zdraví,

    David

  • Hello David,

    Thanks a lot for your reply.

    My goal (customer's request) is to modify the My Assignments Widget to always filter out specific items by default. It's also required to have a different filter based on the location of the widget. The reason for that is to improve the user comfort (provide prefiltered task collection).

    My approach was to:

    A) Create a widget based on csui/my.assignments widget

    B) Add a parameter to the widget manifest to pass the filter into the widget from perspective manager

    C) Modify the collection to filter out unwanted items - using the parse function of my.assignments.model

    I've already done this before, but I've ran into the problem mentioned above. The my.assignments.model containing the parse function is not public. I've solved it before by duplicating all of the private dependencies into my module 1:1.

    The solution broke after upgrading to CS 21 and I don't want to fix it by repeating my mistakes.

    I spent a lot of time trying to figure out a better way on my own, but I couldn't do it.

    Any help / pointer would be greatly appreciated.


    Thank you again for picking this topic up.

    Jan


    PS: There are many "public" models already available through the (csui/bundles/)csui-models.js bundle file, but there are also many that aren't (none from the "csui/models/widget/*" directotry, including my.assignments.model). I don't understand why it's possible to extend some models, but not others. Is there a performance issue by making more of them public? Or are they maybe not meant to be extended?

  • Hi Jan,

    I'll leave comment on your PS to likes of @Ferdinand Prantl who are much better positioned to comment.

    Based on your goal, my natural gravitation would be to use Content Intelligence (assuming customer has access) and the WebReport tile widget.

    This would also give you access to a tag in WebReports that can be used to determine the node ID of the current location the perspective applies to - meaning you can set up a perspective to include your WR tile and use the current location as a parameter to the WR to filter upon.

    As you say, doing this "Smart View native" may require you to replicate the models/dependencies, leaving you with a maintenance consideration.

    I suspect you'll have less "head banging" using WebReports if you can - and less of a maintenance headache moving forward.....

    Regards,

    David

  • Hi David,

    I'm really grateful for the information.

    You've already answered my original question about the bundles, but thanks for proposing the WebReports solution too.

    While the customer has access to WR, they want the modified widget to have the same UI and functionalities as the original... For this reason I think that WR is not the right tool for the job.

    Besides, I've been trying out Smart UI for quite some time, so I'm past the "head banging" parts 😁

    I'm not giving up on (developing for) it and I can see its potential, even more with the recent SDK improvements.

    If I could ask one more thing it would probably be aimed at @Ferdinand Prantl, since I forgot to ask in the PS from my last message: (If there is no reason not to,) would it be possible to add the "csui/models/widget/myassignments.model" into the csui bundle index?

    I can't grasp how difficult (or even possible) it is to make changes to the CS / Smart UI SDK versions, so please don't see it like a "I expect you to snap your fingers and magically solve all my problems" kind of question. 😋

    Have a good weekend,

    Jan

  • Ferdinand Prantl
    Ferdinand Prantl E Community Moderator

    @Jan Velas, Would you mind sharing why your code stopped working with CS 21? Your approach of replacing just the widget's model/collection should be quite resistant against breaking changes.

    About WR

    I recommend a WR if the built-in WR list or table widgets are sufficient and you do not need to customise it a lot. Maintaining multiple Smart UI modules in one big WR file would be a headache. Starting with Smart UI development is a headache too, because the tooling and documentation is not the best, but it is unavoidable for more complicated widgets, or for non-widget extensions. I myself use a modified project generator to start a new project as quickly as possible.

    About Private Modules

    Private modules cannot be used from outside the owning component. Not only because of the risk of a breaking change, but also because the application loading could break. If your bundles got loaded earlier than the bundles including that private module, the application loading would fail.

    A private module dependency is able to be loaded reliably, if you load a public module at first, which is included in the same bundle as the private one:

    csui.require(['a public module'], function (aPublicExport) {
      csui.require(['a private module'], function (aPrivateExport) {
        ...
      })
    })
    

    But that does not change that private modules may disappear any time and break your code.

    Speaking concretely about csui/models/widget/myassignments.model, this module is not private. Private modules can be recognised by /impl/ in their path before the module name. The parent component before /impl/ is the public module that owns it. This module is used outside csui/models in csui/widgets and therefore it has to be public. What is missing, is including it in one of the csui bundle indexes, so that it can be referred from anywhere. You can file a bug about it with the customer support and it should be fixed likely in 22.2.

    There is no workaround to make the requirejsDependencyCheck ignore this. You will have to disable this task temporarily. You can still refer to csui/models/widget/myassignments.model in you dependencies, because this module is bundled inside csui-data.js, which is loaded very early and it is unlikely that your widget would fail loading, when you use it on perspectives inside Smart UI.

    Another workaround is copying the private module, which is used a lot in OpenText too, but it means more maintenance of your growing code base.

    About Filtered MyAssignments

    You approach should work. Generally, widgets are not meant to be inherited from and extended to do something more or something a little different. Composing a new widget from the same shared components should prevent broken code when the original widget gets changed. However, providing the state of the shared components and the documentation is your implementation approach the best one - reuse the whole widget with different data. Inherit, but do not override the widget's functionality, just supply a new model or collection. As long as you implement the expected interface, the widget should work. You could try inheriting the original collection, or write a new one using the mixins to supply the needed interface.

    var MyFilteredAssignmentsView = MyAssignmentsView.extend({
      constructor: function MyFilteredAssignmentsView(options) {
        options || (options = {});
        options.collection = options.context.getCollection(MyFilteredAssignmentCollectionFactory);
        MyAssignmentsView.call(this, options);
      }
    })
    
    var MyFilteredAssignmentCollectionFactory = CollectionFactory.extend({
      propertyPrefix: 'myFilteredAssignments',
      constructor: function MyFilteredAssignmentCollectionFactory(context, options) {
        CollectionFactory.apply(this, arguments);
        var connector = context.getObject(ConnectorFactory);
        this.property = new MyFilteredAssignmentsCollection(undefined, 
          { connector: connector, autoreset: true }));
      },
      fetch: function (options) {
        return this.property.fetch(options);
      }
    });
    
    var MyFilteredAssignmentCollection = MyAssignmentCollection.extend({
      parse: function (response, options) {
        response.results = _.filter(response.results, function (item) {
          return ...;
        });
        this.parseBrowsedState(response, options);
        return this.parseBrowsedItems(response, options);
      }
    })
    


  • Hello @Ferdinand Prantl

    Thanks a lot, this is a treasure trove of information!

    Why the widget broke

    It's actually just the expanded view of the widget that broke. When expanded after the upgrade, the page would just show the generic "Failed to load <widget path>.", and there was nothing in the console.

    I never managed to fix it, but maybe it's because of the way I used the perspective router to display the expanded view. I remember resorting to some mass copy-pasting while changing just the paths, since I didn't know any better. Also having to manually add some things to the out-module for it to work on the server. There are so many things wrong with my project I just decided it's better to refactor it from the start, bringing me here.

    The expanded view is defined in the "behaviors" object of myassignments.view:

    //myassignments.view.js
    ExpandableList: {
            behaviorClass: ExpandingBehavior,
            expandedView: 'csui/widgets/myassignmentstable/myassignmentstable.view',
            ...
    }
    

    I had to duplicate this view as well, because I don't know the way to pass it the collection from myassignments.view. Unless I duplicated the expandedView and modified it's CollectionFactory as well, the filter would be lost when expanded, since the expandedView would use the original model without my filter.

    Before I also didn't know about the "openInPerspective" switch, which I could use to make the widget not open in perspective.

    //myassignments.view.js
    var config = _.defaults({}, module.config(), {
        openInPerspective: true
    });
    

    However, it is possible that the whole perspective issue will be resolved by itself, since I'm confident I've come a long way from then, and I can make things more right.

    About the private modules

    Thanks a lot for your explanation. It's hard to get oriented in the way the Smart UI works, because there are so many "grey zones" where "framework specific things" and "Smart UI specific things" blend together.

    So far everything was up for me to interpret and make sense of, so I'm really glad this forum is a thing.

    About Filtered MyAssignments

    Thanks a lot for the code snippets as well! While we have the same approach it seems like I'm doing some unnecessary work, and I want to keep it as simple as possible to eliminate possible problems.

    I definitely have a lot to think about and try out, but the most important takeaway is that there's nothing wrong with my approach, just the execution.

    I will ask the support to add the my.assignments.model to the bundle index, so I can enable the requirejsDependencyCheck again in the future.

    Thanks for your time and helping me understand.

    Jan