From b6906cc140b624b6977ced124befdc85ccfa1c58 Mon Sep 17 00:00:00 2001 From: Mike Ball Date: Tue, 1 Feb 2011 22:17:05 -0500 Subject: [PATCH 1/7] fixed directions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e8bfb58..663a405 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ In order to start generating documentation, complete the following steps: - install the [SproutGuides package](http://guides-pkg.strobeapp.com/Guides.pkg) - clone SproutGuides from git://github.com/sproutcore/sproutguides.git -- cd into sproutguides and run `guides generate` to generate the output directory +- cd into sproutguides and run `guides build` to generate the output directory Once you've completed these steps, you're ready to start working with SproutGuides. You'll do your work inside the source/ directory. As you make From 9e5093e8a692862e85acc15bf90b15745b230f38 Mon Sep 17 00:00:00 2001 From: Peter Wagenet Date: Mon, 24 Jan 2011 10:33:25 -0800 Subject: [PATCH 2/7] Added section on 'Bindings and Chained Property Paths' to Core Concepts --- source/core_concepts.textile | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/source/core_concepts.textile b/source/core_concepts.textile index 0198296..647b7c2 100644 --- a/source/core_concepts.textile +++ b/source/core_concepts.textile @@ -401,6 +401,29 @@ instead. In this case the local property (where the binding is defined) will be updated when the remote property changes, but changes to the local property will not propagate to the remote property. +h4. Bindings and Chained Property Paths + +Since bindings make use of observers behind the scenes, the same principles +of chained observers applies. This means that if you want to observe a +chained property, you will want to use asterisks in your path as appropriate. + + +MyApp.usersController = SC.ArrayController.create({ + mainUser: SC.Object.create({ + name: 'Joe' + }) +}); + +MyApp.userNameView = SC.LabelView.create({ + valueBinding: 'MyApp.usersController*mainUser.name' +}); + + +Since we used an asterisk in the +valueBinding+ property path, the binding +will be updated if either +mainUser+ or +mainUser.name+ changes. + +h4. Bindings Shorthand + Since bindings are used so often, there's also a shortcut to creating bindings. All you need to do is append +Binding+ to the property name. In the above example, you could instead do: @@ -451,3 +474,5 @@ h3. Changelog * January 12, 2011: initial partial version by "Peter Wagenet":credits.html#pwagenet * January 19, 2011: further updates by "Peter Wagenet":credits.html#pwagenet * January 20, 2011: corrections to "The +init+ Method" and "The Run Loop" by "Peter Wagenet":credits.html#pwagenet +* January 24, 2011: added section on "Bindings and Chained Property Paths" by "Peter Wagenet":credits.html#pwagenet + From 170fed92fd51b7e71763a1ae17ef057ad1125e8b Mon Sep 17 00:00:00 2001 From: Devin Torres Date: Mon, 31 Jan 2011 18:29:11 -0600 Subject: [PATCH 3/7] Rough draft of Core View Concepts --- guides.yml | 2 +- source/views.textile | 246 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 source/views.textile diff --git a/guides.yml b/guides.yml index cf8fe35..0d6af68 100644 --- a/guides.yml +++ b/guides.yml @@ -48,7 +48,7 @@ index: - title: Core View Concepts url: views text: "In this guide, we will cover the basics of SproutCore views, and show you how to customize the built-in SproutCore views for your own use." - construction: true + construction: false - title: Building Your Own Views url: custom_views text: "Most applications will quickly go beyond the built-in SproutCore views. In this guide, we will cover how to build and style your own views." diff --git a/source/views.textile b/source/views.textile new file mode 100644 index 0000000..b878bd3 --- /dev/null +++ b/source/views.textile @@ -0,0 +1,246 @@ +h2. Core Concepts + +The guide covers some of the core concepts of views in SproutCore. By +referring to this guide, you will be able to: + +* Layout your view positions relative to their parent +* Make your views dynamic based on bindings +* Change how your views render and update themselves +* Handle events that occur over your view, e.g. a mouse click +* Animate your views in a performant way + +endprologue. + +h3. Introduction + +After you've created your first SproutCore project, you'll notice the file ++main_page.js+ in your resources folder. This is the main page that your app +appends to the DOM in your +main+ function. Let's start by looking at what +every SproutCore project starts with as it's main page: + + +MyApp.mainPage = SC.Page.design({ + mainPane: SC.MainPane.design({ + childViews: 'labelView'.w(), + labelView: SC.LabelView.design({ + layout: { centerX: 0, centerY: 0, width: 200, height: 18 }, + textAlign: SC.ALIGN_CENTER, + tagName: "h1", + value: "Welcome to SproutCore!" + }) + }) +}); + + +To understand what all of this means, we'll have to look at and understand +what this boilerplate code does. +MyApp.mainPage+ is our main application +page. Your application may end up having several "pages" or you may choose to +swap content into and out of a single page. +MyApp.mainPage+ is an +SC.Page+ +which works by lazily configuring itself. The views within a page are only +awakened when you +get+ the page in the +main.js+ file: + + +MyApp.getPath('mainPage.mainPane').append(); + + ++MyApp.mainPage.mainPane+ is an +SC.MainPane+ which is itself an +SC.Pane+. +There will probably be many panes in your app because a pane is just like a +regular view except that it doesn't need to live within a parent view. These +can be anything from a pallette to a popup to a menu. In this case, our pane +is an +SC.MainPane+ which automatically makes itself main as soon as it's +appended to the doument. + +Our main pane is configured with only one +childView+, an +SC.LabelView+. +SproutCore uses absolute positioning to layout it's views, and this label view +is no different. Giving it a width and height allows us to center the label +directly into the center of the page using +centerX+ and +centerY+ which +specify how many pixels offset from the center we'd like the view. + +NOTE: The +w+ function splits a string by spaces and turns it into an array. +We could have accomplished the same thing by using ++childViews:['labelView']+. + +h3. Laying Out Views on the Page + +Let's say we wanted to make an address book. This address book would have a +list of all your contacts that you could scroll through and see contact +details such as phone numbers and addresses. Let's start the address book +by starting an +SC.WorkspaceView+ which allows us to have a toolbar on the top +and bottom and a view between those two toolbars: + + +MyApp.mainPage = SC.Page.design({ + mainPane: SC.MainPane.design({ + childViews: 'workspaceView'.w(), + workspaceView: SC.WorkspaceView.design({ + contentView: SC.View + }) + }) +}); + + +NOTE: +design+ performs like +extend+ and may register with SproutCore's ++designer+ framework, which is the basis of the interface builder, Greenhouse. +You should never use +design+ outside of a page definition. + +We now have a nice toolbar on the top of the page, but we still don't have a +list of any contacts. If we want a list on the right and details on the left, +it sounds like +SC.SplitView+ is the perfect view to do that, so let's make +that our +contentView+: + + +SC.WorkspaceView.design({ + contentView: SC.SplitView.design({ + dividerThickness: 1, + defaultThickness: 300, + topLeftView: SC.View, + bottomRightView: SC.View + }) +}) + + +Our UI is starting to come together. We can see where we're going to have our +contacts listed and where their details will be displayed. The ++dividerThickness+ is just a aesthetic property since the default really fat/ ++defaultThickness+ will make our fixed view--in this case the ++topLeftView+--300 pixels wide. + +NOTE: When you use values less than 1 for dimensions in SproutCore they're +interpreted as percentages. E.g. +width: 0.5+ tells SproutCore that you want +it to be 50% wide. + +We're going to need to list our contacts, so let's do that to the left, with +our details view to the right. For this, we could use +SC.ListView+ which is +used for generic lists of stacked information where you know each list item's +height. Fortunately, SproutCore includes +SC.SourceListView+ which not only +looks great out of the box, but also provides the default behaviors of a +source list: + + +topLeftView: SC.SourceListView.design({ + content: ["Devin Torres", "Charles Jolley", "Peter Wagenet"] +}) + + +We can then move on to the details of a contact, such as his name, phone +phone number, and address. We can use +SC.LabelView+ for all three in our ++bottomRightView+: + + +bottomRightView: SC.View.design({ + childViews: 'contactDetails'.w(), + contactDetails: SC.View.design({ + layout: { top: 50, left: 50, bottom: 50, right: 50 }, + childViews: 'nameLabel phoneLabel addressLabel'.w(), + nameLabel: SC.LabelView.design({ + layout: { width: 500, height: 18 }, + value: "Contact name" + }), + phoneLabel: SC.LabelView.design({ + layout: { top: 40, width: 500, height: 18 }, + value: "Contact phone number" + }), + addressLabel: SC.LabelView.design({ + layout: { top: 80, width: 500, height: 500 }, + value: "Contact address" + }) + }) +}) + + +The labels are wrapped in +contactDetails+ so they can be cushioned from the +edges of the parent view to see them better. +phoneLabel+ and +addressLabel+ +are given a +top+ to clear each other so they don't overlap, and ++addressLabel+ is given a large +height+ so a long address can wrap within the +label. Right now these labels are only placeholders until we hook their values +to a binding. + +h3. Binding View Properties to Values + +Instead of having our source list content and our label values in our +views, let's bind them to some controllers. We'll start by creating an +SC.ArrayController for our contacts and an SC.ObjectController that +proxies individual contact objects: + + +MyApp.contactsController = SC.ArrayController.create({ + content: [ + SC.Object.create({ + name: "Devin Torres", + phone: "(555) 391-1419", + address: "214 12th St. Austin, TX 78701" + }), + SC.Object.create({ + name: "Charles Jolley", + phone: "(555) 749-1585", + address: "378 16th St. Austin, TX 78701" + }), + SC.Object.create({ + name: "Peter Wagenet", + phone: "(555) 856-3750", + address: "935 2nd St. Austin, TX 78701" + }) + ] +}); + +MyApp.contactController = SC.ObjectController.create({ + contentBinding: SC.Binding.from('MyApp.contactsController.selection').single() +}); + + +Now we can bind our source list to the +SC.ArrayController+ to get a dynamic +list of contacts and individual contact selection support: + + +SC.SourceListView.design({ + contentValueKey: 'name', + contentBinding: 'MyApp.contactsController.content', + selectionBinding: 'MyApp.contactsController.selection' +}) + + +With the contacts selectable, the only thing missing is dynamic label values +for the contact's name, phone, and address: + + +SC.View.design({ + layout: { top: 50, left: 50, bottom: 50, right: 20 }, + childViews: 'nameLabel phoneLabel addressLabel'.w(), + nameLabel: SC.LabelView.design({ + layout: { width: 500, height: 18 }, + valueBinding: SC.Binding.oneWay('MyApp.contactController.name') + }), + phoneLabel: SC.LabelView.design({ + layout: { top: 40, width: 500, height: 18 }, + valueBinding: SC.Binding.oneWay(''MyApp.contactController.phone') + }), + addressLabel: SC.LabelView.design({ + layout: { top: 80, width: 500, height: 500 }, + valueBinding: SC.Binding.oneWay(''MyApp.contactController.address') + }) +}) + + +NOTE: Using +SC.Binding.oneWay+ is not necessary here, but using one way +bindings when you don't need changes from the view reflected back to the +controller--which is the case for these label views--is more performant. + +The contact information will now change automatically whenever a new contact +is selected. + +h3. Custom View Rendering + +SproutCore provides three ways to customize how your view looks. You can +simply give it a class name and add it to the view's +classNames+ array +property to style it using CSS, override it's +render+ and +update+ methods to +use it's own custom HTML, or use an +SC.RenderDelegate+ to render and update +all views of the view type. + +h4. +render+ and +update+ + +Let's make the interface a bit more visually appealing and user friendly by +giving a usage hint when no contact has been selected yet. + +h3. Changelog + +* January 12, 2011: initial version by "Devin Torres":credits.html#dtorres From 68a5570c2c9fe1145d76c2be7879a26c353fa95d Mon Sep 17 00:00:00 2001 From: Devin Torres Date: Tue, 1 Feb 2011 21:15:10 -0600 Subject: [PATCH 4/7] Add .bundle and bin to .gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index de1dc5f..d6185cf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ -output .DS_Store +.bundle +bin +output From 6420aba6835cfd51a5d378fc0e218f50a2d22ac2 Mon Sep 17 00:00:00 2001 From: Jacek Becela Date: Sun, 30 Jan 2011 14:07:32 +0100 Subject: [PATCH 5/7] Fix typo: being -> begin --- source/core_concepts.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/core_concepts.textile b/source/core_concepts.textile index 647b7c2..a282407 100644 --- a/source/core_concepts.textile +++ b/source/core_concepts.textile @@ -299,7 +299,7 @@ i.e. +this.notifyPropertyChange('value')+. In some cases you may find that you are updating a number of properties at once. If you have a lot of observers that depend on these properties, you may find them getting called more often than necessary. In this case, you -can use +beingPropertyChanges+ and +endPropertyChanges+ to wrap the your +can use +beginPropertyChanges+ and +endPropertyChanges+ to wrap the your property change calls. This will cause all change notifications to happen once +endPropertyChanges+ is called and will prevent unnecssary duplicate notifications. From 59b7c2ef866033ebdf360cd501cef280556399df Mon Sep 17 00:00:00 2001 From: Peter Wagenet Date: Tue, 1 Feb 2011 19:26:38 -0800 Subject: [PATCH 6/7] Marking View guide as under construction until reviewed --- guides.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides.yml b/guides.yml index 0d6af68..cf8fe35 100644 --- a/guides.yml +++ b/guides.yml @@ -48,7 +48,7 @@ index: - title: Core View Concepts url: views text: "In this guide, we will cover the basics of SproutCore views, and show you how to customize the built-in SproutCore views for your own use." - construction: false + construction: true - title: Building Your Own Views url: custom_views text: "Most applications will quickly go beyond the built-in SproutCore views. In this guide, we will cover how to build and style your own views." From 904d3ee54538268bd4d424a67032fff21d371639 Mon Sep 17 00:00:00 2001 From: Devin Torres Date: Tue, 1 Feb 2011 21:44:43 -0600 Subject: [PATCH 7/7] Enhance bindings section of core view guide --- source/views.textile | 269 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 250 insertions(+), 19 deletions(-) diff --git a/source/views.textile b/source/views.textile index b878bd3..238ccc2 100644 --- a/source/views.textile +++ b/source/views.textile @@ -3,11 +3,10 @@ h2. Core Concepts The guide covers some of the core concepts of views in SproutCore. By referring to this guide, you will be able to: -* Layout your view positions relative to their parent -* Make your views dynamic based on bindings +* Layout your views relative to their parent +* Make your views dynamic with bindings * Change how your views render and update themselves -* Handle events that occur over your view, e.g. a mouse click -* Animate your views in a performant way +* Handle events that occur over your view like a mouse click endprologue. @@ -47,8 +46,8 @@ MyApp.getPath('mainPage.mainPane').append(); There will probably be many panes in your app because a pane is just like a regular view except that it doesn't need to live within a parent view. These can be anything from a pallette to a popup to a menu. In this case, our pane -is an +SC.MainPane+ which automatically makes itself main as soon as it's -appended to the doument. +is an +SC.MainPane+ which automatically makes itself the main pane as soon as +it's appended to the doument. Our main pane is configured with only one +childView+, an +SC.LabelView+. SproutCore uses absolute positioning to layout it's views, and this label view @@ -155,7 +154,7 @@ are given a +top+ to clear each other so they don't overlap, and label. Right now these labels are only placeholders until we hook their values to a binding. -h3. Binding View Properties to Values +h3. Binding Views to Controllers Instead of having our source list content and our label values in our views, let's bind them to some controllers. We'll start by creating an @@ -168,17 +167,20 @@ MyApp.contactsController = SC.ArrayController.create({ SC.Object.create({ name: "Devin Torres", phone: "(555) 391-1419", - address: "214 12th St. Austin, TX 78701" + address: "214 12th St. Austin, TX 78701", + website: 'http://www.linkedin.com/in/devintorres' }), SC.Object.create({ name: "Charles Jolley", phone: "(555) 749-1585", - address: "378 16th St. Austin, TX 78701" + address: "378 16th St. Austin, TX 78701", + website: 'http://www.linkedin.com/in/charlesjolley' }), SC.Object.create({ name: "Peter Wagenet", phone: "(555) 856-3750", - address: "935 2nd St. Austin, TX 78701" + address: "935 2nd St. Austin, TX 78701", + website: 'http://www.linkedin.com/in/wagenet' }) ] }); @@ -212,11 +214,11 @@ SC.View.design({ }), phoneLabel: SC.LabelView.design({ layout: { top: 40, width: 500, height: 18 }, - valueBinding: SC.Binding.oneWay(''MyApp.contactController.phone') + valueBinding: SC.Binding.oneWay('MyApp.contactController.phone') }), addressLabel: SC.LabelView.design({ layout: { top: 80, width: 500, height: 500 }, - valueBinding: SC.Binding.oneWay(''MyApp.contactController.address') + valueBinding: SC.Binding.oneWay('MyApp.contactController.address') }) }) @@ -226,20 +228,249 @@ bindings when you don't need changes from the view reflected back to the controller--which is the case for these label views--is more performant. The contact information will now change automatically whenever a new contact -is selected. +is selected. Our +bottomRightView+ should now look like this: -h3. Custom View Rendering + +SC.View.design({ + childViews: 'usageHint contactDetails'.w(), + usageHint: SC.View.design({ + classNames: 'myapp-usage-hint', + isVisibleBinding: SC.Binding.from('MyApp.contactsController.hasSelection').not(), + childViews: [SC.LabelView.design({ + layout: { width: 300, height: 22, centerX: 0, centerY: 0 }, + tagName: 'h1', + textAlign: SC.ALIGN_CENTER, + value: "Select a contact" + })] + }), + contactDetails: SC.View.design({ + layout: { top: 50, left: 50, bottom: 50, right: 20 }, + childViews: 'nameLabel phoneLabel addressLabel'.w(), + nameLabel: SC.LabelView.design({ + layout: { width: 500, height: 18 }, + valueBinding: 'MyApp.contactController.name' + }), + phoneLabel: SC.LabelView.design({ + layout: { top: 40, width: 500, height: 18 }, + valueBinding: 'MyApp.contactController.phone' + }), + addressLabel: SC.LabelView.design({ + layout: { top: 80, width: 500, height: 500 }, + valueBinding: 'MyApp.contactController.address' + }) + }) +}) + + +h3. Custom View Styling and Rendering SproutCore provides three ways to customize how your view looks. You can simply give it a class name and add it to the view's +classNames+ array -property to style it using CSS, override it's +render+ and +update+ methods to -use it's own custom HTML, or use an +SC.RenderDelegate+ to render and update -all views of the view type. +property to style it using CSS, override it's +render+ method to use it's own +custom HTML, or use an +SC.RenderDelegate+ to render and update all views of +the view type. -h4. +render+ and +update+ +h4. Using CSS to Style a View Let's make the interface a bit more visually appealing and user friendly by -giving a usage hint when no contact has been selected yet. +giving a usage hint when no contact has been selected yet. We ca create a +generic view and add the class name +myapp-usage-hint+ to the +classNames+ +array, and then a label view as a child to display the hint: + + +SC.View.design({ + classNames: 'myapp-usage-hint', + isVisibleBinding: SC.Binding.from('MyApp.contactsController.hasSelection').not(), + childViews: [SC.LabelView.design({ + layout: { width: 300, height: 22, centerX: 0, centerY: 0 }, + tagName: 'h1', + textAlign: SC.ALIGN_CENTER, + value: "Select a contact" + })] +}) + + +Right now it looks rather small, so let's add some style to it. Create a file +in the +resources+ directory and name it +theme.css+: + + +.myapp-usage-hint { + background-color: #eee; +} + +.myapp-usage-hint h1 { + font-family: "Helvetica Neue Light", "HelveticaNeue-Light", inherit; + font-size: 28px !important; + line-height: 20px !important; + color: #999; +} + + +Now we have nice looking instructions for when a user first starts using the +app. You can add as many class names to +classNames+ as you want for any view. + +h4. The +render+ and +update+ Methods + +The +render+ method is used to generate the view's HTML and is called whenever +the view first renders itself and any subsequent updates to the view can be +handled by an +update+ method after that. + +NOTE: As soon as you start overriding methods such as +render+ and +update+, +your views really need to be in their own files under the +views+ directory. +A good rule of thumb is that +main_page.js+ should never have anything longer +than a one line function. + +The +update+ method is called whenever a property that is defined on the view's ++displayProperties+ array is changed on the view. The +render+ method is +passed a +context+ variable which is an +SC.RenderContext+. +SC.RenderContext+ +is used to queue up and build HTML for a view. You build HTML by calling ++context.push()+ and passing it HTML: + + +CreepyApp.CharlesJolleyView = SC.View.extend({ + render: function (context) { + context.push("

Charles Jolley is 31 years young...

"); + } +}); +
+ +You can also use +context.begin()+ and +context.end()+ to begin building a tag +and push arguments that are "stringable" (have a toString) into it: + + +CreepyApp.CharlesJolleyView = SC.View.extend({ + displayProperties: ['name', 'age'], + name: 'Charles Jolley', + age: 31, + render: function (context) { + var name = this.get('name'), age = this.get('age'); + var context = context.begin('h1'); + context.push(name, ' is going to be ', age+1, ' years young soon...'); + context.end(); + } +}); + + +The +update+ method is passed a jQuery handle of the view's layer, the DOM +element that belongs to the view. You can use it and all of jQuery's API to +manipulate the DOM and update your view: + + +CreepyApp.CharlesJolleyView = SC.View.extend({ + displayProperties: ['name', 'age'], + name: 'Charles Jolley', + age: 31, + render: function (context) { + var name = this.get('name'), age = this.get('age'); + var context = context.begin('h1'); + context.push('', name, ''); + context.push(' is going to be '); + context.push('', age+1, ''); + context.push(' years young soon...'); + context.end(); + }, + update: function (jquery) { + var h1 = jquery.find('h1'); + h1.find('.name').text(this.get('name')); + h1.find('.age').text(this.get('age')); + } +}) + + +Say we wanted to make the name of the contact bigger in the details. To do +this, let's create a custom view called +MyApp.ContactNameView+ that will put +the contact name in an h1 tag: + + +MyApp.ContactNameView = SC.View.extend({ + displayProperties: ['value'], + render: function (context) { + context.push('

', this.get('value'), '

'); + }, + update: function (jquery) { + jquery.find('h1').text(value); + } +}); +
+ +h4. Render Delegates + +Render delegates are an easy way to manage theming your apps. You can create +a render delegate to manage the rendering of your views for you and add it to +the theme you're using. Render delegates also have a +render+ and +update+ +method, but their first argument is the +dataSource+, or the view delegating +to them. Let's recreate the last scenario using an +SC.RenderDelegate+ that +we're going to create in +render_delegates/contact_name.js+: + + +SC.AceTheme.contactNameRenderDelegate = SC.RenderDelegate.create({ + render: function (dataSource, context) { + context.push('

', dataSource.get('value'), '

'); + }, + update: function (dataSource, jquery) { + jquery.find('h1').text(dataSource.get('value')); + } +}); +
+ +Now we have to tell our view to use our new render delegate to draw itself: + + +MyApp.ContactNameView = SC.View.extend({ + displayProperties: ['value'], + renderDelegateName: 'contactNameRenderDelegate' +}); + + +h3. View Events + +A SproutCore view hierarchy can be viewed like a pond, if the water is touched +it ripples outwards affecting all the water around it, or in our case, all the +parent views. +SC.View+ extends from +SC.Responder+, inheriting the ability to +react to DOM events such as +mouseDown+ and +mouseEntered+. Let's highlight the +contact name when it's being hovered over and take the user to the contact's +website if it's clicked: + + +MyApp.ContactNameView = SC.View.extend({ + displayProperties: ['value'], + renderDelegateName: 'contactNameRenderDelegate', + mouseEntered: function () { + var jquery = this.$(); + jquery.css('cursor', 'pointer'); + jquery.css('color', '#356aa0'); + return YES; + }, + mouseExited: function () { + var jquery = this.$(); + jquery.css('cursor', 'normal'); + jquery.css('color', '#000'); + return YES; + }, + mouseDown: function (evt) { + MyApp.openWebsite(); + return YES; + } +}); + + +And in +core.js+: + + +openWebsite: function () { + var website = MyApp.contactController.get('website'); + window.open(website); +} + + +You should return +YES+ when you've handled the event, otherwise it would +propagate to all of it's parent views. + +WARNING: Actions such as these would usually be handled by a statechart and +are handled here for the purposes of demonstration. + +SproutCore views are powerful, and when coupled with bindings, controllers, a +few models, and a data source make developing applications fast and easy. h3. Changelog