When working with view objects you are going to be constantly dealing with how to position them within your web application. And in SproutCore, positioning views is kind of but not exactly like positioning elements in a typical web page using CSS. With views, you are going to be thinking about where a view is anchored within its parent view, what it’s relative position is to where the view is anchored, and its size.

To get a basic idea of just how SproutCore positions views, it’s best to work with a simple application. Start by first creating a new SproutCore application called layout by running the following command:

sc-init layout

Now run your application just to make sure everything is working as expected. Do this by running the following command within the layout directory created for you by sc-init:

sc-server

If everything went okay, you should be able to open up your favorite browser and go to http://localhost:4020/layout and see the screen shown below:

viewpic1

Okay, great. Now, to make it obvious just how the views are being positioned, we need to modify the look of the views. We will do this by creating a new CSS file called layout.css and placing it in the layout/apps/layout/english.lproj directory. In the CSS file enter the following:


h1 {
  border-style: solid;
  border-width: 1px;
  border-color: red;
  background-color: white;
}

Awesome. If you refresh your browser you should get the following:

viewpic2

Now open up the main_page.js file located in the layout/apps/layout/english.lproj directory. This file is where you typically setup the views. You should see the following code (excluding comments):


Layout.mainPage = SC.Page.design({

  mainPane: SC.MainPane.design({
    childViews: 'labelView'.w(),
    
    labelView: SC.LabelView.design({
      layout: { centerX: 0, centerY: 0, width: 100, height: 18 },
      tagName: "h1", value: "Hello World"
    })
  })

});

You’ll notice that there is a SC.LabelView object that is being setup within a SC.MainPane object. The main pane is the parent view of the label view. This parent-child view relationship is important in how the views get positioned by SproutCore. In the label view, the part we care about is the layout property. By default, the label is centered in the middle of the parent view (centerX: 0, centerY: 0), and its width is 100 pixels and height is 18 pixels. Let’s update the code so that the label view is called labelView1 and its value is “Label View 1”. In addition, let’s add two more label views called labelView2 and labelView3. We’ll position labelView2 so that it is positioned in the top-left corner of the pane and labelView3 is position in the bottom-right corner. The code should be the following:


Layout.mainPage = SC.Page.design({

  mainPane: SC.MainPane.design({
    childViews: 'labelView1 labelView2 labelView3'.w(),
    
    labelView1: SC.LabelView.design({
      layout: { centerX: 0, centerY: 0, width: 100, height: 18 },
      tagName: "h1", 
      value: "Label View 1"
    }),

    labelView2: SC.LabelView.design({
      layout: { left: 0, top: 0, width: 100, height: 18 },
      tagName: "h1", 
      value: "Label View 2"
    }),

    labelView3: SC.LabelView.design({
      layout: { bottom: 0, right: 0, width: 100, height: 18 },
      tagName: "h1", 
      value: "Label View 3"
    })
  })

});

So if you save your file and then reload your browser you should see the following:

viewpic3

Label views 1, 2 and 3 are all children of the main pane and as such SproutCore will do all the necessary work to make sure the views are positioned correctly. Note that although we are just using label views to understand how views are positioned, this still applies for most of the other views in SproutCore framework.

As of now, all the views have been given an absolute width and height, but that doesn’t have to be the case. In fact, let’s change our views so that the second label view and the third label view stretch out across the top and bottom of the browser window. Update the layout of the second and third label views to be the following:


...

labelView2: SC.LabelView.design({
  layout: { left: 0, top: 0, height: 18 },
  tagName: "h1", 
  value: "Label View 2"
}),

labelView3: SC.LabelView.design({
  layout: { bottom: 0, right: 0, height: 18 },
  tagName: "h1", 
  value: "Label View 3"
})
...

Save the file and refresh your browser. You should get the following:

viewpic4

Huh. So by removing the width from each layout that informed SproutCore to stretch the views horizontally. Cool. Well, what about the first label view. How about if we would like to fill in the all the vacant space in the middle of the window instead of just sitting lonely in the middle. Let’s update the layout property of label view 1 to be the following:


...
labelView1: SC.LabelView.design({
  layout: { left: 0 },
  tagName: "h1", 
  value: "Label View 1"
}),
...

If you save your file and reload your browser you’ll end up getting the following:

viewpic5

What the? Somethings not right! You’ll notice that the middle of the window is now all white, but no where do you see the text “Label View 1”. This doesn’t seem to make any sense. Actually it does. SproutCore followed your layout instructions and made label view 1 fill up the entire window, but label view 1 is partly hidden behind label view 2 and 3 because of the how the views have been layered. Go back to the code for a second. In the main pain you see a line of code reads the following:

childViews: ‘labelView1 labelView2 labelView3’.w(),

Because of the ordering, SproutCore is instructed to first render labelView1 as HTML and then render label views 2 and 3 as HTML. In reality, SproutCore is just consecutively placing the views as HTML one at a time and the browser uses its default CSS styling rules to layer the elements as normally does. So SproutCore is making use of the web browser to position and layer the element accordingly. Great. So what if we were to reorder to the main pane’s children to be the following:

childViews: ‘labelView2 labelView3 labelView1’.w(),

We’ll get the following screen:

viewpic6

Yay! We now see label view 1… er, wait, now label view 2 and 3 are no longer visible. Actually, they are there but because of the browser layers the HTML elements labels 2 and 3 are hidden behind label view 1. D’oh. Well then how do we get all three label views to appear correctly so that they aren’t overlapping each other? Turns out we have to do a little finessing with the positioning of label view 1. Notice how label view 2 and label view 3 each have a height of 18 pixels? Well that tells us that we have to shrink the height of label view 1 based on the other views’ size. Update label view 1’s layout property to be the following:

layout: { left: 0, top: 18, bottom: 18 }

Now save and refresh your browser. You should get the following:

viewpic7

Yes! We finally got all three label views to be correctly visible. Basically, we told SproutCore to push label view 1 down from the top of the parent view (main pane) by 18 pixels and push it up from the bottom of the parent view by 18 pixels. This means we have to be mindful of both where a view is anchored and its size and position relative to the other sibling views within the same parent view.

Okay, this is great stuff. But we aren’t done just yet. Remember that it is important to know that the position of a view is based on the parent-child relationship. So let’s look at a bit more of a complex example where we place one view inside of another view instead of just in the main pane. We are going to add a generic view (a SC.View object) to our pane and put label view 1 inside of it. Update your code so that it is the following:


Layout.mainPage = SC.Page.design({

  mainPane: SC.MainPane.design({
    childViews: 'plainView labelView2 labelView3'.w(),
    
    plainView: SC.View.design({
      childViews: 'labelView1'.w(),
      layout: { centerX: 0, centerY: 0, width: 200, height: 200 },
      classNames: ['plainView'],
			
      labelView1: SC.LabelView.design({
        layout: { left: 0, centerY: 0, width: 100, height: 18 },
	tagName: "h1", 
	value: "Label View 1"
      })	
    }),

    labelView2: SC.LabelView.design({
      layout: { left: 0, top: 0, height: 18 },
      tagName: "h1", 
      value: "Label View 2"
    }),

    labelView3: SC.LabelView.design({
      layout: { bottom: 0, right: 0, height: 18 },
      tagName: "h1", 
      value: "Label View 3"
    })
  })

});

Our generic view is called plainView and labelView1 is now a child of it. We positioned label view 1 so that it will be placed left of center of the plain view. We also need to update our CSS file to be the following:


h1, .plainView {
  border-style: solid;
  border-width: 1px;
  border-color: red;
  background-color: white;
}

Finally, save all files and refresh the browser. You should get the following:

viewpic8

Boo-ya! We see that label view 1 is indeed positioned relative to the parent view’s boundaries. Go ahead and resize the window. Everything works as expected. Again, SproutCore is doing the rendering into HTML so that the browser at the end will correctly place all the elements according to its CSS rules. Check out the HTML source. You’ll see how everything is setup just so by SproutCore in order to give us the right look and feel.

So there ya are. While this was a basic look at how views are positioned, I hope it helped those new to the framework get a better grip of just how views are actually positioned in SproutCore. If you interested in more layout details you can dig through the view.js file’s comments located in the frameworks/foundation/views folder in the SproutCore root directory. Around line 1456 there is some decent comments on all the various layout attributes and how they can be combined together.

Advertisements