Titanium JIRA Archive
Titanium SDK/CLI (TIMOB)

[TIMOB-7673] Ti API: "replaceAt", "insertAt", and "getAt" functionality for view-like UI components

GitHub Issuen/a
TypeNew Feature
PriorityMedium
StatusClosed
ResolutionFixed
Resolution Date2014-04-17T17:34:56.000+0000
Affected Version/sRelease 2.0.0
Fix Version/s2014 Sprint 08, 2014 Sprint 08 SDK, Release 3.3.0
ComponentsTiAPI
Labelsapi, module_api, qe-closed-3.3.0, qe-testadded
ReporterIvan Skugor
AssigneePedro Enrique
Created2012-02-13T03:24:34.000+0000
Updated2015-08-14T14:39:50.000+0000

Description

Problem

In dynamic scenarios, we often need to add view components at specific positions in parent view container. For example, if parent view container has 3 child view components, we may want to add view at first position ("add" always adds at the end). Currently, this is not possible to do natively in Titanium, but it's very useful feature which Titanium should have supported natively.

Workaround

As a workaround, this approach can be used: https://gist.github.com/1485751 It works fine for small number of child elements, but for large number of child elements there could be performance drop.

Solution

Implement those functionality for all view-like components (for those components that can contain other components). I suggest following syntax:
view.replaceAt(1, Ti.UI.createButton()); //replaces child element at index 1 with a button
view.insertAt(1, Ti.UI.createButton()); //adds a button at index 1

Comments

  1. process 2012-02-15

    It's the implementation of this : https://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/cl/UIView insertSubview:atIndex:
  2. Stephen Tramer 2012-02-15

    We already offer the zindex property of views to determine where they are displayed in a hierarchical layer: http://developer.appcelerator.com/apidoc/mobile/latest/Titanium.UI.View.zIndex-property.html If there is a rationale for this beyond view layer positioning the ticket must be updated to include: * A sample use case * A valid justification for the feature * An explanation of why existing features cannot be used in place It is worth noting that, in addition, the implementation of zindex on iOS places the view in the layer position exactly as this ticket describes is desirable.
  3. process 2012-02-16

    Hi Stephen, here a simple but usefull case : Imagine, you want to create a huge scrollableView with 500 views Inside (for my case). It's impossible to load every views on start as you know there will be a lack of memory. So you need to populate dynamically your scrollableView. A part of the algorithm : - On start you create 3 views with page 1, 2, 3. - Imagine myapp.myScrollableView.currentPage is on 2, at index 1. If there's a scroll to the right, the current page is 3 and myapp.myScrollableView.currentPage = 2. There's no page to the right ... so you make myapp.myScrollableView.addView(myapp.createPage(4)) and then you remove the first myapp.myScrollableView.removeView(myapp.myScrollableView.views[0]). There's no problem with this case but ... The other part that cause problem - Imagine you have pages 2, 3 and 4, myapp.myScrollableView.currentPage = 1 and the current page is 3. - Now there's a scroll to the left so the current page is 2, myapp.myScrollableView.currentPage = 0 and there's no page on left side so ... you need to add a view to the left, that is to say ADDING a page to the BEGINNING of myapp.myScrollableView.views and remove the last views myapp.myScrollableView.removeView(myapp.myScrollableView.views[2] A solution on ipad 2 : If you use a simple array (myarray) in which you can add any item at the beginning etc. You just have to write : myapp.myScrollableView.views = myarray but this will make the view winkle.
       (function(){
       myapp = {]
       myapp.win = Ti.UI.createWindow();
       
       var page1 = Ti.UI.createView({ backgroundColor:'#123' });
       var page2 = Ti.UI.createView({ backgroundColor:'#246' });
       var page3 = Ti.UI.createView({ backgroundColor:'#48b' });
       
       myapp.myScrollableView = Ti.UI.createScrollableView({
         views:[page1,page2,page3],
         showPagingControl:true
       });
       
       myapp.myScrollableView.addEventListener('scroll', myapp.scrollHandler);
       myapp.scrollHandler = function(){
           // if go to the right
          if(myapp.myScrollableView.currentPage == 2){
             // add at the end
             // remove the first
              
          }
          // if go to the left
          if(myapp.myScrollableView.currentPage == 0){
             // add to the begin
             // remove the last
          }
       }
       })();
       
       
  4. process 2012-02-16

  5. process 2012-02-16

    Thank you very much :)
  6. Ivan Skugor 2012-02-16

    OK, I experimented with "zIndex" property and it does exactly what is wanted in this ticket (on Android also). But why that property is called "*z*Index" when it arranges elements in x or y axis when parent element's layout property is set to "horizontal" or "vertical"? I thought it should arrange elements in z axis only (it seems natural to assume "zIndex" does similar thing as it does in CSS: https://developer.mozilla.org/en/Understanding_CSS_z-index). Because of that, I think that name "zIndex" is not the best choice. I assume "xyzIndex" name is out of question :) ("stackIndex" maybe ... ) so at least behavior of "zIndex" should be properly documented (by that I mean documentation should contain explanation of "zIndex" behavior when different layouts are defined). IMHO, real solution would be: * leave "zIndex" property, but make it work only when parent element's "layout" property is set to "absolute" (there is no point in using "zIndex" with "vertical" and "horizontal" layout because there is no way elements will overlap) * implement methods from this ticket (as it is described below "Solution" header) That way: * "zIndex" would do exactly what its name says * it would be much clearer how to add element at specific position When developer asks how to add element at specific position, the answer would be: * use "insertAt" method instead of: * use "zIndex" (that is not really "z" index), but you need to make sure parent element has layout property defined as "horizontal" or "vertical" because if layout is set to "absolute" "zIndex" does what its name says it does. Also, you need to set "zIndex" to all child elements to get this work and you need to be careful not to f*ck up indexes when inserting at specific position (different views can have same values for "zIndex"). In the end ... I know this is fairly trivial issue that can be solved relatively easily. But leaving things as they are now leads developers to frustration because Titanium behavior differences from behavior they are familiar with and they need to do work and loose time to find and implement basic functionality that is naturally to have. I personally don't have great interest in this ticket (hey, I wrote a workaround long time ago), I reported this because I think this kinds of basic functionality, that is intuitive and easy to use, makes developers happy and is must have in framework. :)
  7. Stephen Tramer 2012-02-16

    It's called zindex because that's what it does. Where views are placed in the children of a view determines the layered display order.
  8. Ivan Skugor 2012-02-17

    But it doesn't do what its name says: In CSS 2.1, each box has a position in three dimensions. In addition to their horizontal and vertical positions, boxes lie along a "z-axis" and are formatted one on top of the other. Z-axis positions are particularly relevant when boxes overlap visually. (from CSS 2.1 Section 9.9.1 - Layered presentation) Here is a picture also: http://msdn.microsoft.com/en-us/library/bb980110%28v=vs.95%29.aspx When parent element has layout defined as "absolute", then it does what its name says (and it's identical functionality to the one that already exists in other technologies), otherwise it does something else (it's similar thing, but it works in other dimensions). Anyway ... we usually need to insert element at specific position after content has rendered. To do that, we need to add z-indexes initially (before content is rendered) when child views are added:
       parent.add(child1);
       child1.zIndex = parent.children.length - 1; //zIndex = 0
       parent.add(child2);
       child2.zIndex = parent.children.length - 1; //zIndex = 1
       parent.add(child3);
       child3.zIndex = parent.children.length - 1; //zIndex = 2
       ...
       
    Now, if I want to add element at second position (index 1):
       parent.add(child22);
       child22.zIndex = 1;
       
    Since there is "child2" with "zIndex" set to 1, to get this work I need to increment "zIndex" to all child elements:
       for (var i = index + 1; i < parent.children.length; ++i) {
           parent.children[i].zIndex = i;
       }
       
    Compare that approach with suggested approach:
       parent.add(child1);
       parent.add(child2);
       parent.add(child3);
       ...
       
       parent.insertAt(1, child22);
       
    Two times less code, no DRY, no need to wrap that functionality into Titanium UI wrappers, better performance, right semantic. If don't find that reasons justified, then I don't have anything else to say, except: iQuit :) P.S. JFTR, this feature request is labeled as "iOS", while my initial request was made for all platforms.
  9. Stephen Tramer 2012-02-17

    There are a couple of things at issue here but I'm going to start an internal technical discussion about this feature. The way zindex operates with horiz/vert layout needs to be resolved at the very least.
  10. Stephen Tramer 2012-02-17

    Ivan - It's been requested that you give PE some more information about the zindex problems. The documentation is supposed to be correct - if zindex is doing anything other than interacting with the z-positioning layer of views on either platform (positioning within the subview order is a nice side-effect on iOS only it seems), that is a bug. Addressing zindex issues directly is a separate issue, so please file a bug report for that instead of adding to this feature request. It seems like the feature that you're requesting would only be particularly useful for vert/horiz layout (again, zindex should be doing the view layering positioning correctly for composite, since where a view is placed in the hierarchy there only determines where it lives in the layered draw order) but both platforms could easily support this capability as an optional argument to Ti.UI.View.add().
  11. Bryan Hughes 2012-02-17

    On Mobile Web zIndex follows ths CSS spec as described by Ivan: it arranges elements along the z-axis only. In a horizontal or vertical layout, zIndex has no effect, since controls can't overlap. Internally we already have an _insertAt(view,index) method that we use all the time. It is extremely useful for doing horizontal/vertical layout operations. For example, we use it when inserting a tab into a tabgroup. This method internally calls the insertBefore method exposed by the DOM. I am _*strongly*_ in favor of making z-index only affect the z-axis and adding an insertAt method (or some similar method/set of methods). Having z-index affect something other than the z-axis is very unintuitive, and also makes it context sensitive (which is a parity and documentation hassle).
  12. Ivan Skugor 2012-02-20

    Stephen, I don't know what "PE" stands for? :) In the documentation, "zIndex" is explained this way: "Z index position relative to other sibling views." That seems very generic explanation, maybe there should be mention of z-axis (for people not familiar with "zIndex" from other technologies) and that it works (will work) only when "absolute" layout is used. Optional argument to "add" method seems fine from technical point of view, although I like clear and dont-make-me-think method names (like "insertBefore", "insertAt", "insertAfter" - although they all could use just one method in the background).
  13. Arthur Evans 2012-04-18

    Based on the comments, it sounds like there's some support for this feature request, so reopening.
  14. Bryan Hughes 2012-04-18

    +1 I really really think we should implement this feature. FWIW Mobile Web already has this feature using the (undocumented, hidden) method _insertAt().
  15. Ivan Skugor 2012-04-24

  16. Kabriel Robichaux 2012-10-11

    It surprises me that even Ti 3.0 doesn't yet have the ability to add a child at a specific index. This seems like a foundational feature that is clearly supported by the underlying system. What will it take to get insertAt added? It sounds like the zIndex conversation might have sidetracked this request. zIndex doesn't seem to have any influence over the x,y position of children views in horizontal or vertical layouts (as was implied by some posts here).
  17. Pedro Enrique 2012-10-29

    This is most useful when using layout vertical/horizontal. zIndex does not work in that case.

    Example code

        
        var win = Ti.UI.currentWindow;
        win.backgroundColor = '#ccc';
        win.layout = 'vertical';
        
        var red = Ti.UI.createView({
            backgroundColor: 'red',
            width: Ti.UI.FILL,
            height: 90,
            zIndex: 2
        });
        
        var green = Ti.UI.createView({
            backgroundColor: 'green',
            width: Ti.UI.FILL,
            height: 100, 
            zIndex: 3
        });
        
        var blue = Ti.UI.createView({
            backgroundColor: 'blue',
            width: Ti.UI.FILL,
            height: 120,
            zIndex: 1
        });
        win.add(red);
        win.add(blue);
        win.add(green);
        
  18. Blain Hamon 2012-11-05

    Note that because zIndex works differently, having a zIndex on views and using these proposed methods is UNDEFINED. In other words, if we implement them, and you use them, and you have an issue with views that have zIndex, that issue is invalid.
  19. Ingo Muschenetz 2013-02-28

    While we would really like to achieve this for 3.1.0, we don't have the bandwidth with other outstanding features. My goal is to get this in 3.2.
  20. Blain Hamon 2013-02-28

    I want to add that we depricate zindex. This would replace the original need for zIndex, and be much more performant.
  21. John C. Bland II 2013-03-03

    +100 for adding insertAt. I'd also vote for removeAt and a getViewAt; basically rounding out the ability to add, remove and get a view at a specific location. Maybe another ticket could be added for a removeAllViews() method too. ;-)
  22. John C. Bland II 2013-03-03

    Here is an initial implementation of an insertViewAt in use in my app: https://gist.github.com/johncblandii/5074660. It is based on the gist linked above but modified. I haven't performance tested it but I definitely don't like the idea of removing every element first then adding them back. For now, it will work but would love to see a true implementation.
  23. Pedro Enrique 2013-04-09

    I have created a very simple extension to TiViewProxy for iOS, the drawback is that the parent view flashes... https://gist.github.com/pec1985/5348549
  24. Pedro Enrique 2013-12-10

    PR: https://github.com/appcelerator/titanium_mobile/pull/5109
  25. Pedro Enrique 2013-12-10

    Test code for the above PR:
        var win = Titanium.UI.createWindow({
        	layout: 'vertical'
        });
        
        var optionsView = Ti.UI.createView({
        	height: 50,
        	backgroundColor: '#ccc'
        });
        
        var insertButton = Ti.UI.createButton({
        	left: 0,
        	width: 100,
        	title: 'insertAt 2'
        });
        
        var replaceButton = Ti.UI.createButton({
        	right: 0,
        	width: 100,
        	title: 'replaceAt 4'
        });
        
        optionsView.add(insertButton);
        optionsView.add(replaceButton);
        
        win.add(optionsView);
        
        for(var i = 0; i < 10; i++) {
        	win.add(Ti.UI.createView({
        		height: 30,
        		backgroundColor: 'green',
        		top: 5
        	}));
        }
        win.open();
        
        var flag1 = 0;
        var flag2 = 0;
        insertButton.addEventListener('click', function() {
        	win.insertAt({
        		view: Ti.UI.createLabel({
        			top: 5, width: Ti.UI.FILL, height: 50,
        			text: 'insertAt',
        			backgroundColor: flag1++ % 2 ? 'blue' : 'red'
        		}),
        		position: 2
        	});
        });
        replaceButton.addEventListener('click', function() {
        	win.replaceAt({
        		view: Ti.UI.createLabel({
        			top: 5, width: Ti.UI.FILL, height: 50,
        			text: 'replaceAt',
        			backgroundColor: flag2++ % 2 ? 'yellow' : 'orange'
        		}),
        		position: 4
        	});
        });
        
  26. Vishal Duggal 2014-04-17

    We have only implemented insertAt and replaceAt on iOS and Android. getAt is not required since you can always do view.children[i] to get equivalent functionality PR merged
  27. Priya Agarwal 2014-04-23

    Verified with test environment: Appc-Studio:3.3.0.201404211130 sdk:3.3.0.v20140422163054 acs:1.0.14 alloy:1.4.0-dev npm:1.3.2 titanium:3.3.0-dev titanium-code-processor:1.1.1-beta1 xCODE:5.1.1 Device:Iphone5s(7.1.1),LG-P970(v4.0.4) Ti API "replaceAt" and "insertAt" working fine in both android and ios.

JSON Source