Call a parent component from child in ENYO

Hi Team,

In the below code @ function
circleTap,
i want to call the parent class components
browsecategories & browseProducts to hide() & show() them.
enyo.kind({
  name: 'OB.UI.ProductBrowser',
  classes: 'row-fluid',
  components: [{
		classes : 'span6',
		components : [ {
			kind : 'OB.UI.BrowseProducts',
			name : 'browseProducts'
		} ]
	},
  {
    classes: 'span6',
    components: [{
      kind: 'OB.UI.BrowseCategories',
      name: 'browseCategories'
    }]
  }
  ],
  init: function () {
	this.$.browseProducts.hide();
	this.browseProductCategories = this.$.browseCategories;
    this.$.browseCategories.$.listCategories.categories.on('click', function (category) {
      this.$.browseCategories.hide();
      this.$.browseProducts.$.listProducts.loadCategory(category);
      this.$.browseProducts.show();
    }, this);
    
//    this.$.backToCategories.on('click', function () {
//    	  alert("on click");
////        this.$.browseCategories.hide();
////        this.$.browseProducts.$.listProducts.loadCategory(category);
////        this.$.browseProducts.show();
//      });
  }
});

enyo.kind({
  name: 'OB.UI.BrowseCategories',
  style: 'overflow:auto; margin: 5px;',
  components: [{
    style: 'background-color: #ffffff; color: black; padding: 5px',
    components: [{
      kind: 'OB.UI.ListCategories',
      name: 'listCategories'
    }]
  }]
});

enyo.kind({
  name: 'OB.UI.BrowseProducts',
  style: 'margin: 5px;',
  components: [{
    style: 'background-color: #ffffff; color: black; padding: 5px',
    components: [{
      kind: 'OB.UI.ListProducts',
      name: 'listProducts'
    }]
  }]
});

enyo.kind({
  kind: 'OB.UI.ScrollableTableHeader',
  name: 'OB.UI.CategoryListHeader',
  style: 'padding: 10px; border-bottom: 1px solid #cccccc;',
  components: [{
    style: 'line-height: 27px; font-size: 18px; font-weight: bold;',
    name: 'title',
    content: OB.I18N.getLabel('OBPOS_LblCategories')
  }]
});

enyo.kind({
  name: 'OB.UI.ListCategories',
  components: [{
    name: 'categoryTable',
    scrollAreaMaxHeight: '445px',
    listStyle: 'list',
    kind: 'OB.UI.ScrollableTable',
    renderHeader: 'OB.UI.CategoryListHeader',
    renderEmpty: 'OB.UI.RenderEmpty',
    renderLine: 'OB.UI.CustomRenderCategory'
  }],

  init: function () {
    var me = this;
    this.categories = new OB.Collection.ProductCategoryList();
    this.$.categoryTable.setCollection(this.categories);

    function errorCallback(tx, error) {
      OB.UTIL.showError("OBDAL error: " + error);
    }

    function successCallbackBestSellerProducts(products, context) {
      if (products && products.length > 0) {
        var virtualBestSellerCateg = new OB.Model.ProductCategory();
        virtualBestSellerCateg.createBestSellerCategory();
        context.categories.add(virtualBestSellerCateg, {
          at: 0
        });
      }
      context.me.categories.reset(context.categories.models);
    }

    function successCallbackCategories(dataCategories, me) {
      var bestSellerCriteria, context;
      if (dataCategories && dataCategories.length > 0) {
        //search products
        bestSellerCriteria = {
          'bestseller': 'true'
        };
        context = {
          me: me,
          categories: dataCategories
        };
        OB.Dal.find(OB.Model.Product, bestSellerCriteria, successCallbackBestSellerProducts, errorCallback, context);
      } else {
        me.categories.reset();
      }
    }

    OB.Dal.find(OB.Model.ProductCategory, null, successCallbackCategories, errorCallback, this);
  }
});

//This header is set dynamically
//use scrollableTableHeaderChanged_handler method of scrollableTable to manage changes
//me.$.productTable.setHeaderText(category.get('_identifier'));
enyo.kind({
  kind: 'OB.UI.ScrollableTableHeader',
  name: 'OB.UI.ProductListHeader',
  style: 'padding: 10px; border-bottom: 1px solid #cccccc;',
  components: [{
    style: 'line-height: 27px; font-size: 18px; font-weight: bold;',
    name: 'title'
  }],
  setHeader: function (valueToSet) {
    this.$.title.setContent(valueToSet);
  }
});

enyo.kind({
  name: 'OB.UI.ListProducts',
  events: {
    onAddProduct: '',
    onShowLeftSubWindow: ''
  },
  components: [{
	  components: [{
		    kind: 'OB.UI.Button',
		    name: 'button',
		    style: 'font-weight:bold',
		    content: 'Back to Category',
//		    classes: 'btn btn-icon-backspace',
		    ontap: "circleTap"
		  }],
  },
    {
    kind: 'OB.UI.ScrollableTable',
    name: 'productTable',
    scrollAreaMaxHeight: '574px',
    renderHeader: 'OB.UI.ProductListHeader',
    renderEmpty: 'OB.UI.RenderEmpty',
    renderLine: 'OB.UI.RenderProduct'
  }],
  circleTap: function() {
	  alert("in click");
	  console.log(this);
	  console.log(this.parent.parent.$.browseProducts);
	  $("#terminal_containerWindow_pointOfSale_toolbarpane_catalog_productBrowser_browseProducts_control").hide();
	  $("#terminal_containerWindow_pointOfSale_toolbarpane_catalog_productBrowser_browseCategories").show();
  },
  init: function () {
    this.inherited(arguments);
    this.products = new OB.Collection.ProductList();
    this.$.productTable.setCollection(this.products);
    this.products.on('click', function (model) {
      this.doAddProduct({
        product: model
      });
    }, this);
  },

  loadCategory: function (category) {
    var criteria, me = this;

    function errorCallback(tx, error) {
      OB.UTIL.showError("OBDAL error: " + error);
    }

    function successCallbackProducts(dataProducts) {
      if (dataProducts && dataProducts.length > 0) {
    	console.log(category.get('id'));
        me.products.reset(dataProducts.models);
      } else {
        me.products.reset();
      }
      //      TODO
      me.$.productTable.getHeader().setHeader(category.get('_identifier'));
    }

    if (category) {
      if (category.get('id') === 'OBPOS_bestsellercategory') {
        criteria = {
          'bestseller': 'true'
        };
      } else {
        criteria = {
          'productCategory': category.get('id')
        };
      }
      OB.Dal.find(OB.Model.Product, criteria, successCallbackProducts, errorCallback);
    } else {
      this.products.reset();
      this.title.text(OB.I18N.getLabel('OBPOS_LblNoCategory'));
    }
  }
});


I tried in couple of ways like
this.parent.parent.$.browsecategories & this.parent.parent.$.browseProducts
AND
this.parent.parent.browsecategories & this.parent.parent.browseProducts
but no of these seems to work, as a temporary fix i am using the old jquery style i.e., calling on the div ID. But that gives me only one time control.
Can you please suggest a solution for this one. Thanks!

Comments

  • You need to throw an event in your child that is caught by your parent. You could add an event to your ListProduct kind called 'hidetoggle' and then in your BrowseProducts kind add to your ListProduct component a handler 'onhidetoggle' which would toggle whether you are showing or hiding the component.
  • Thanks Strider73. You saved my life.
  • edited May 2013
    As @strider73 says, working with events is the Enyo way. But if you _need_ to do it, you can access them directly:

    this.container refers to the kind that contains this kind.
    this.owner refers to the kind that owns this kind (the one that receives this kind events).

    You can go in depth with it:

    this.container.owner, this.owner.owner.container, and so on...

    But that's not the way you're supposed to work with Enyo, as enyo.kind() it's supossed to be reusable, and using these tricks breaks it's reusability.
  • BTW: this / $ / container / owner / parent / child / components
    would be helpful to have some comprehensive description for these.
  • this.$ = the components a kind owns
    this.container = the DOM element where the control is rendered
    this.owner = the component that owns the kind, events bubble there first
    this.child = not an Enyo construct
    this.components = the components a kind declares (or adds dynamically via createComponent/s)
  • Thanks ! Now I need to visualize this in a picture .. :)
  • also:
    this.controls = all controls contained by this (in other words, where this.control[i].container === this)
    this.children = ordered list of controls contained by this. Will be rendered into DOM in this order. If you include the addBefore member to createComponent, this is the array that is altered.
    getClientControls() = any control where isChrome is not truthy
  • edited May 2013
    Actually, while this.child doesn't exist, this.children does. It's best to think about layers here.

    A view in Enyo is usually based on enyo.Control or a subkind of it. enyo.Control inherits from enyo.UIComponent which represents something that can be rendered into the UI. This exists because of the Canvas library which needed UI elements that didn't correspond to DOM nodes.

    UIComponent introduces the ideas of parent and children and container.
    • parent is the instance that events pass through on the way up the tree
    • children is an array of components that have this one as its parent. This is needed to bubble down events through a tree.
    • container is the instance that physically contains the rendered item
    • controls is the array of components that have this as a container, this is needed to control the order of rendering.
    Going one level higher is enyo.Component which defines the components hierarchy. The idea of "owner" comes from this object level.
    • owner is the instance that owns this component
    • $ is the hash that maps names to owned components

  • I'm a little confused - is owner where events go or parent? Based on this example looks like parent - http://jsfiddle.net/TkjEB/

    I'd like to redirect events for a created component but I think changing the parent causes reflow issues.
  • there's two different paths for event propagation:

    There's bubbling through the object tree which uses the handlers arrays on each object to map an event to a method

    There's also the "owner-specified" event handling where you declare a property on the owner component in the form "onEventName" that has a string naming a method in the owner.
  • I'm going for the owner-specified case but events to don't seem to go to the owner specified. Or maybe there is a mistake in my example? http://jsfiddle.net/TkjEB/
  • Interestingly enough, this works: http://jsfiddle.net/sugardave/TkjEB/1/

    I think this is a bug.
  • First, a problem with your fiddle code:

    you can't pass "doButtonTap" as the direct handler for tap because it expects a single "event" object, but will be passed a sender and event object, which results in your button instance being treated as the event.

    However, this is working as expected. Event bubbling will go through the parent chain. It's only for the cases where the instance has a property with the event name where we use owner lookup.
  • @slojs I didn't mean to add to any confusion, I just learned this subtle difference in event propagation as it relates to Components and UiComponents.
  • Thank you but confused still :( Not sure what you mean by 'Its only for the cases where the instance has a property with the event name where we use owner lookup' - maybe a snippet would help?

    Or maybe I should start a new post with my situation - I have two sibling Controls, control 1 & control 2 & I'd like control 1 to receive the events that control 2 generates first. I thought setting control 2's owner to control 1 would do it, but are you saying I really need to have a common parent control that receives the events from control 2 and redirects them to control 1?

    Thanks again & @sugardave no worries thank you!
  • The owner is only used for this case:
        { kind: "Button", ontap: "processButton" }
    
    Here, you specify a property with ontap that tells the button to look at it's owner for a method named "processButton".

    However, along with this mechanism, we also do bubbling, and that happens through the parent chain. So, the code would look for handlers["ontap"] first on the control, then on its parent, then on its parent until we get to the root.

    On to your second topic -- how to you delegate events from one control to go through another. We actually have made a whole new controller mechanism in 2.3 to handle this kind of delegation, but there controllers aren't UI objects, they're just in the event handling chain.

    This is all controlled by a method called "getBubbleTarget". For components, this just returns owner, but for UIComponents, this will return parent. For your override, you might find it simple to override this method than modifying parent, as parent has other duties.

  • Ah crystal clear now - thank you, really awesome support guys!!
Sign In or Register to comment.