From 737ef267ca7ca8df28d8d12471e4633fa1d1c0b5 Mon Sep 17 00:00:00 2001 From: Colin Campbell Date: Wed, 21 Jul 2010 12:30:55 -0700 Subject: [PATCH 01/15] ImageButton renderer does not have a focus method, need to check inside SC.ButtonView --- frameworks/desktop/views/button.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frameworks/desktop/views/button.js b/frameworks/desktop/views/button.js index d73d84274..f30484625 100644 --- a/frameworks/desktop/views/button.js +++ b/frameworks/desktop/views/button.js @@ -322,7 +322,7 @@ SC.ButtonView = SC.View.extend(SC.Control, SC.Button, SC.StaticLayout, } else if (!this._isFocused && (buttonBehavior!==SC.PUSH_BEHAVIOR)) { this._isFocused = YES ; this.becomeFirstResponder(); - if (this.get('isVisibleInWindow')) { + if (this.get('isVisibleInWindow') && this.renderer.focus) { this.renderer.focus(); } } From 9fb9f7d7d9ca0e8b85dc4b32318f4cdf463562be Mon Sep 17 00:00:00 2001 From: Colin Campbell Date: Fri, 23 Jul 2010 16:15:29 -0700 Subject: [PATCH 02/15] Fixed issue with SC.TabView CSS applying to its container CSS to all container views inside of it, rather than just its own direct descendant --- themes/ace/resources/theme.css | 2 +- themes/ace/src/containers/tab/tab.css | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/themes/ace/resources/theme.css b/themes/ace/resources/theme.css index e3a311fc7..1bf8422fe 100644 --- a/themes/ace/resources/theme.css +++ b/themes/ace/resources/theme.css @@ -1756,7 +1756,7 @@ img.disclosure.button.closed.active { background: static_url("images/1.png") no- .sc-theme .sc-view.ace.sc-small-size.sc-button-view.def.active .button-right { background: static_url("images/1.png") no-repeat -16px -2231px; } .sc-theme .sc-view.ace.sc-small-size.sc-button-view.def.active .button-middle { background: static_url("images/2.png") repeat-x 0 -1564px; } .sc-theme .sc-view.ace.sc-tab-view { overflow: visible; } -.sc-theme .sc-view.ace.sc-tab-view .sc-container-view { +.sc-theme .sc-view.ace.sc-tab-view > .sc-container-view { -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; diff --git a/themes/ace/src/containers/tab/tab.css b/themes/ace/src/containers/tab/tab.css index 91a46ad2f..a505976ba 100644 --- a/themes/ace/src/containers/tab/tab.css +++ b/themes/ace/src/containers/tab/tab.css @@ -1,9 +1,10 @@ @view(sc-tab-view) { overflow: visible; - .sc-container-view { - -sc-border-radius: 5px; - border: 1px solid #999; - -webkit-box-shadow: 1px 1px 1px rgba(255, 255, 255, .3); - overflow:hidden; - } +} + +@view(sc-tab-view) > .sc-container-view { + -sc-border-radius: 5px; + border: 1px solid #999; + -webkit-box-shadow: 1px 1px 1px rgba(255, 255, 255, .3); + overflow:hidden; } \ No newline at end of file From 347cdead2e74bf98e139198aca4d5687f22b122c Mon Sep 17 00:00:00 2001 From: Colin Campbell Date: Fri, 23 Jul 2010 16:42:11 -0700 Subject: [PATCH 03/15] Added WellView renderers to the BaseTheme and AceTheme, as well as default styles --- frameworks/desktop/renderers/well.js | 30 +++++++++++++++++++++++++ frameworks/desktop/views/well.js | 23 +++++-------------- themes/ace/resources/theme.css | 8 +++++++ themes/ace/src/containers/well/well.css | 6 +++++ themes/ace/src/containers/well/well.js | 22 ++++++++++++++++++ 5 files changed, 71 insertions(+), 18 deletions(-) create mode 100644 frameworks/desktop/renderers/well.js create mode 100644 themes/ace/src/containers/well/well.css create mode 100644 themes/ace/src/containers/well/well.js diff --git a/frameworks/desktop/renderers/well.js b/frameworks/desktop/renderers/well.js new file mode 100644 index 000000000..43cc7b996 --- /dev/null +++ b/frameworks/desktop/renderers/well.js @@ -0,0 +1,30 @@ +// ========================================================================== +// Project: SproutCore - JavaScript Application Framework +// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors. +// Portions ©2008-2009 Apple Inc. All rights reserved. +// License: Licened under MIT license (see license.js) +// ========================================================================== + +/** @class + @extends SC.Renderer + @since SproutCore 1.1 +*/ +SC.BaseTheme.renderers.Well = SC.Renderer.extend({ + render: function(context) { + context.push("
", + "
", + "
", + "
", + "
", + "
", + "
", + "
", + "
"); + + if (this.contentProvider) this.contentProvider.renderContent(context); + }, + + update: function() {} +}); + +SC.BaseTheme.renderers.well = SC.BaseTheme.renderers.Well.create(); \ No newline at end of file diff --git a/frameworks/desktop/views/well.js b/frameworks/desktop/views/well.js index 195d91e06..78c4c5e8d 100644 --- a/frameworks/desktop/views/well.js +++ b/frameworks/desktop/views/well.js @@ -48,25 +48,12 @@ SC.WellView = SC.ContainerView.extend( } }, - /** - The render method for the WellView simply add the html necessary for - the border. - - */ + createRenderer: function(theme) { + return theme.well(); + }, - render: function(context, firstTime) { - if(firstTime){ - context.push("
", - "
", - "
", - "
", - "
", - "
", - "
", - "
", - "
"); - } - sc_super(); + updateRenderer: function() { + }, /** diff --git a/themes/ace/resources/theme.css b/themes/ace/resources/theme.css index 1bf8422fe..079c60692 100644 --- a/themes/ace/resources/theme.css +++ b/themes/ace/resources/theme.css @@ -1755,6 +1755,14 @@ img.disclosure.button.closed.active { background: static_url("images/1.png") no- .sc-theme .sc-view.ace.sc-small-size.sc-button-view.def.active .button-left { background: static_url("images/1.png") no-repeat -24px -2231px; } .sc-theme .sc-view.ace.sc-small-size.sc-button-view.def.active .button-right { background: static_url("images/1.png") no-repeat -16px -2231px; } .sc-theme .sc-view.ace.sc-small-size.sc-button-view.def.active .button-middle { background: static_url("images/2.png") repeat-x 0 -1564px; } +.sc-theme .sc-view.ace.sc-well-view { + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; + border: 1px solid #999999; + -webkit-box-shadow: 1px 1px 1px rgba(255, 255, 255, 0.3); + overflow: hidden; +} .sc-theme .sc-view.ace.sc-tab-view { overflow: visible; } .sc-theme .sc-view.ace.sc-tab-view > .sc-container-view { -moz-border-radius: 5px; diff --git a/themes/ace/src/containers/well/well.css b/themes/ace/src/containers/well/well.css new file mode 100644 index 000000000..94278b005 --- /dev/null +++ b/themes/ace/src/containers/well/well.css @@ -0,0 +1,6 @@ +@view(sc-well-view) { + -sc-border-radius: 5px; + border: 1px solid #999; + -webkit-box-shadow: 1px 1px 1px rgba(255, 255, 255, .3); + overflow:hidden; +} \ No newline at end of file diff --git a/themes/ace/src/containers/well/well.js b/themes/ace/src/containers/well/well.js new file mode 100644 index 000000000..800d47e25 --- /dev/null +++ b/themes/ace/src/containers/well/well.js @@ -0,0 +1,22 @@ +// ========================================================================== +// Project: SproutCore - JavaScript Application Framework +// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors. +// Portions ©2008-2009 Apple Inc. All rights reserved. +// License: Licened under MIT license (see license.js) +// ========================================================================== + +sc_require('src/theme'); + +/** @class + @extends SC.BaseTheme.renderers.Well + @since SproutCore 1.1 +*/ +SC.AceTheme.renderers.Well = SC.BaseTheme.renderers.Well.extend({ + render: function(context) { + if (this.contentProvider) this.contentProvider.renderContent(context); + }, + + update: function() {} +}); + +SC.AceTheme.renderers.well = SC.AceTheme.renderers.Well.create(); \ No newline at end of file From 35d89c3822d4766514ab16d321190844e5d788f3 Mon Sep 17 00:00:00 2001 From: Colin Campbell Date: Fri, 23 Jul 2010 18:55:45 -0700 Subject: [PATCH 04/15] ListItem renderer now uses renderContents/updateContents functions to make it easier to subclass --- frameworks/desktop/renderers/list_item.js | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/frameworks/desktop/renderers/list_item.js b/frameworks/desktop/renderers/list_item.js index 3365b9a34..7433de3da 100644 --- a/frameworks/desktop/renderers/list_item.js +++ b/frameworks/desktop/renderers/list_item.js @@ -32,6 +32,14 @@ SC.BaseTheme.renderers.ListItem = SC.Renderer.extend({ context.addStyle("left", indent * (level+1)); } + this.renderContents(context); + + context = context.end(); // end outline + + this.resetChanges(); + }, + + renderContents: function(context) { this.renderDisclosure(context); this.renderCheckbox(context); this.renderIcon(context); @@ -39,16 +47,18 @@ SC.BaseTheme.renderers.ListItem = SC.Renderer.extend({ this.renderRightIcon(context); this.renderCount(context); this.renderBranch(context); - - context = context.end(); // end outline - - this.resetChanges(); }, update: function() { this.updateControlRenderer(); this.$().setClass(this.calculateClassNames()); + this.updateContents(); + + this.resetChanges(); + }, + + updateContents: function() { this.updateDisclosure(); this.updateCheckbox(); this.updateIcon(); @@ -56,8 +66,6 @@ SC.BaseTheme.renderers.ListItem = SC.Renderer.extend({ this.updateRightIcon(); this.updateCount(); this.updateBranch(); - - this.resetChanges(); }, didAttachLayer: function(layer){ From e6a6947c7fac3a6ea0258c2ced9fc4583ee43c46 Mon Sep 17 00:00:00 2001 From: Colin Campbell Date: Fri, 23 Jul 2010 18:56:39 -0700 Subject: [PATCH 05/15] SC.ListItemView checks rightIcon property when determining if click occurred within it --- frameworks/desktop/views/list_item.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frameworks/desktop/views/list_item.js b/frameworks/desktop/views/list_item.js index acda8e1c3..afad805b8 100644 --- a/frameworks/desktop/views/list_item.js +++ b/frameworks/desktop/views/list_item.js @@ -352,7 +352,7 @@ SC.ListItemView = SC.View.extend( */ _isInsideRightIcon: function(evt) { var del = this.displayDelegate ; - var rightIconKey = this.getDelegateProperty('hasContentRightIcon', del) ; + var rightIconKey = this.getDelegateProperty('hasContentRightIcon', del) || !SC.none(this.rightIcon); return rightIconKey && this._isInsideElementWithClassName('right-icon', evt); }, From 96edc8f99a904b1425b8639919e84d6ae80401b4 Mon Sep 17 00:00:00 2001 From: Colin Campbell Date: Fri, 6 Aug 2010 13:09:50 -0230 Subject: [PATCH 06/15] Fixed SC.FormRowView from blowing out passed label value --- frameworks/forms/views/form_row.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frameworks/forms/views/form_row.js b/frameworks/forms/views/form_row.js index d9cad1890..d89b20ee7 100644 --- a/frameworks/forms/views/form_row.js +++ b/frameworks/forms/views/form_row.js @@ -171,7 +171,7 @@ SC.FormRowView.mixin({ } // now, create a hash (will be used by the parent form's exampleRow) if (!ext) ext = {}; - ext.label = label; + if (label) ext.label = label; ext.childViews = ["_singleField"]; ext._singleField = fieldType; return ext; From b5a9612485aa097e3e82984761a5169bdf91a4ba Mon Sep 17 00:00:00 2001 From: Colin Campbell Date: Fri, 6 Aug 2010 13:14:35 -0230 Subject: [PATCH 07/15] Initial tests of SC.FixturesDataSource, issues remain with tests (functionality should be fine) --- frameworks/datastore/data_sources/fixtures.js | 48 +++- .../tests/data_sources/fixtures_latency.js | 214 ++++++++++++++++++ 2 files changed, 255 insertions(+), 7 deletions(-) create mode 100644 frameworks/datastore/tests/data_sources/fixtures_latency.js diff --git a/frameworks/datastore/data_sources/fixtures.js b/frameworks/datastore/data_sources/fixtures.js index 2a103f0cb..6472db129 100644 --- a/frameworks/datastore/data_sources/fixtures.js +++ b/frameworks/datastore/data_sources/fixtures.js @@ -64,6 +64,7 @@ SC.FixturesDataSource = SC.DataSource.extend( /** @private */ fetch: function(store, query) { + var latency = this.get('latency'); // can only handle local queries out of the box if (query.get('location') !== SC.Query.LOCAL) { @@ -75,7 +76,14 @@ SC.FixturesDataSource = SC.DataSource.extend( } if (this.get('simulateRemoteResponse')) { - this.invokeLater(this._fetch, this.get('latency'), store, query); + // during tests, invokeLater is undefined + if (this.invokeLater) { + this.invokeLater(this._fetch, latency, store, query); + } else { + setTimeout(function(that) { + that._fetch(store, query); + }, latency, this); + } } else this._fetch(store, query); }, @@ -84,7 +92,6 @@ SC.FixturesDataSource = SC.DataSource.extend( Actually performs the fetch. */ _fetch: function(store, query) { - // NOTE: Assumes recordType or recordTypes is defined. checked in fetch() var recordType = query.get('recordType'), recordTypes = query.get('recordTypes') || [recordType]; @@ -115,14 +122,20 @@ SC.FixturesDataSource = SC.DataSource.extend( if (!ret) return ret ; if (this.get('simulateRemoteResponse')) { - this.invokeLater(this._retrieveRecords, latency, store, storeKeys); + // during tests, invokeLater is undefined + if (this.invokeLater) { + this.invokeLater(this._retrieveRecords, latency, store, storeKeys); + } else { + setTimeout(function(that) { + that._retrieveRecords(store, storeKeys); + }, latency, this); + } } else this._retrieveRecords(store, storeKeys); return ret ; }, _retrieveRecords: function(store, storeKeys) { - storeKeys.forEach(function(storeKey) { var ret = [], recordType = SC.Store.recordTypeFor(storeKey), @@ -146,7 +159,14 @@ SC.FixturesDataSource = SC.DataSource.extend( if (!ret) return ret ; if (this.get('simulateRemoteResponse')) { - this.invokeLater(this._updateRecords, latency, store, storeKeys); + // during tests, invokeLater is undefined + if (this.invokeLater) { + this.invokeLater(this._updateRecords, latency, store, storeKeys); + } else { + setTimeout(function(that) { + that._updateRecords(store, storeKeys); + }, latency, this); + } } else this._updateRecords(store, storeKeys); return ret ; @@ -172,7 +192,14 @@ SC.FixturesDataSource = SC.DataSource.extend( var latency = this.get('latency'); if (this.get('simulateRemoteResponse')) { - this.invokeLater(this._createRecords, latency, store, storeKeys); + // during tests, invokeLater is undefined + if (this.invokeLater) { + this.invokeLater(this._createRecords, latency, store, storeKeys); + } else { + setTimeout(function(that) { + that._createRecords(store, storeKeys); + }, latency, this); + } } else this._createRecords(store, storeKeys); return YES ; @@ -206,7 +233,14 @@ SC.FixturesDataSource = SC.DataSource.extend( if (!ret) return ret ; if (this.get('simulateRemoteResponse')) { - this.invokeLater(this._destroyRecords, latency, store, storeKeys); + // during tests, invokeLater is undefined + if (this.invokeLater) { + this.invokeLater(this._destroyRecords, latency, store, storeKeys); + } else { + setTimeout(function(that) { + that._destroyRecords(store, storeKeys); + }, latency, this); + } } else this._destroyRecords(store, storeKeys); return ret ; diff --git a/frameworks/datastore/tests/data_sources/fixtures_latency.js b/frameworks/datastore/tests/data_sources/fixtures_latency.js new file mode 100644 index 000000000..5c049165a --- /dev/null +++ b/frameworks/datastore/tests/data_sources/fixtures_latency.js @@ -0,0 +1,214 @@ +// ========================================================================== +// Project: SproutCore - JavaScript Application Framework +// Copyright: ©2006-2009 Apple Inc. and contributors. +// License: Licensed under MIT license (see license.js) +// ========================================================================== +/*globals module ok equals same test MyApp Sample */ + +var store, fds, storeKey1,storeKey2, Test; + +module("SC.FixturesDataSource latency", { + setup: function() { + SC.RunLoop.begin(); + + Test = {}; + Test.FixturesDataSource = SC.FixturesDataSource.extend({ + simulateRemoteResponse: YES, + latency: 250 + }); + + var Sample = (window.Sample= SC.Object.create()); + Sample.File = SC.Record.extend({ test:'hello'}); + + // files + Sample.File.FIXTURES = [ + { guid: '10', name: 'Home', url: '/emily_parker', isDirectory: true, parent: null, children: 'Collection'}, + { guid: '11', name: 'Documents', fileType: 'documents', url: '/emily_parker/Documents', isDirectory: true, parent: '10', children: 'Collection', createdAt: 'June 15, 2007', modifiedAt: 'October 21, 2007', filetype: 'directory', isShared: false}, + { guid: '137',name: 'Library', fileType: 'library', url: '/emily_parker/Library', isDirectory: true, parent: '10', children: 'Collection', createdAt: 'June 15, 2007', modifiedAt: 'October 21, 2007', filetype: 'directory', isShared: false}, + { guid: '12', name: 'Movies', fileType: 'movies', url: '/emily_parker/Movies', isDirectory: true, parent: '10', children: 'Collection', createdAt: 'June 15, 2007', modifiedAt: 'June 15, 2007', filetype: 'directory', isShared: true, sharedAt: 'October 15, 2007', sharedUntil: 'March 31, 2008', sharedUrl: '2fhty', isPasswordRequired: true}, + { guid: '134',name: 'Music', fileType: 'music', url: '/emily_parker/Music', isDirectory: true, parent: '10', children: 'Collection', createdAt: 'June 15, 2007', modifiedAt: 'June 15, 2007', filetype: 'directory', isShared: true, sharedAt: 'October 15, 2007', sharedUntil: 'March 31, 2008', sharedUrl: '2fhty', isPasswordRequired: true}, + { guid: '135',name: 'Pictures', fileType: 'pictures', url: '/emily_parker/Pictures', isDirectory: true, parent: '10', children: 'Collection', createdAt: 'June 15, 2007', modifiedAt: 'June 15, 2007', filetype: 'directory', isShared: true, sharedAt: 'October 15, 2007', sharedUntil: 'March 31, 2008', sharedUrl: '2fhty', isPasswordRequired: true}, + { guid: '13', name: 'Auto Insurance', fileType: 'folder', url: '/emily_parker/Documents/Auto%20Insurance', isDirectory: true, parent: '11', children: 'Collection', createdAt: 'June 15, 2007', modifiedAt: 'October 21, 2007', filetype: 'directory', isShared: false}, + { guid: '150', name: 'Birthday Invitation.pdf', fileType: 'file', url: '/emily_parker/Documents/Birthday%20Invitation', isDirectory: false, parent: '11', createdAt: 'October 17, 2007', modifiedAt: 'October 21, 2007', filetype: 'pdf', isShared: false}, + { guid: '136', name: 'Software', fileType: 'software', url: '/emily_parker/Software', isDirectory: true, parent: '10', children: 'Collection', createdAt: 'June 15, 2007', modifiedAt: 'June 15, 2007', filetype: 'directory', isShared: true, sharedAt: 'October 15, 2007', sharedUntil: 'March 31, 2008', sharedUrl: '2fhty', isPasswordRequired: true} + ]; + + store = SC.Store.create().from(Test.FixturesDataSource.create()); + }, + + teardown: function() { + Test = store = null; + SC.RunLoop.end(); + } +}); + +test("Verify find() loads all fixture data", function() { + var result = store.find(Sample.File), + rec, storeKey, dataHash; + + var timer = setTimeout(function() { + ok(false, 'FixturesDataSource did not return response within 2s'); + window.start(); + }, 2000); + + result.addObserver('status', function() { + if (result.get('status') & SC.Record.READY) { + clearTimeout(timer); + + equals(result.get('length'), Sample.File.FIXTURES.get('length'), 'should return records for each item in FIXTURES'); + + // verify storeKeys actually return Records + var idx, len = result.get('length'), expected = []; + for(idx=0;idx Date: Sun, 8 Aug 2010 13:09:50 -0230 Subject: [PATCH 08/15] Explicitly check falsity of isReady in SC._object_className so searching for class names in tests work --- frameworks/runtime/system/object.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frameworks/runtime/system/object.js b/frameworks/runtime/system/object.js index 1c8f074e0..fe95581b5 100644 --- a/frameworks/runtime/system/object.js +++ b/frameworks/runtime/system/object.js @@ -868,7 +868,7 @@ SC.kindOf = function(scObject, scClass) { This method is used to allow classes to determine their own name. */ SC._object_className = function(obj) { - if (!SC.isReady) return ''; // class names are not available until ready + if (SC.isReady === NO) return ''; // class names are not available until ready if (!obj._object_className) findClassNames() ; if (obj._object_className) return obj._object_className ; From 0336f947d45f70cb0a1ca2883507324dd395eef9 Mon Sep 17 00:00:00 2001 From: Colin Campbell Date: Sun, 8 Aug 2010 13:15:07 -0230 Subject: [PATCH 09/15] Implemented polymorphic to-one relationships for record attributes through SC.PolymorphicSingleAttribute --- .../models/polymorphic_single_attribute.js | 185 ++++++++++++++++++ frameworks/datastore/models/record.js | 13 ++ .../tests/models/polymorphic/single.js | 124 ++++++++++++ 3 files changed, 322 insertions(+) create mode 100644 frameworks/datastore/models/polymorphic_single_attribute.js create mode 100644 frameworks/datastore/tests/models/polymorphic/single.js diff --git a/frameworks/datastore/models/polymorphic_single_attribute.js b/frameworks/datastore/models/polymorphic_single_attribute.js new file mode 100644 index 000000000..81b656ea9 --- /dev/null +++ b/frameworks/datastore/models/polymorphic_single_attribute.js @@ -0,0 +1,185 @@ +// ========================================================================== +// Project: SproutCore - JavaScript Application Framework +// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors. +// Portions ©2008-2009 Apple Inc. All rights reserved. +// License: Licensed under MIT license (see license.js) +// ========================================================================== + +sc_require('models/single_attribute'); + +/** @class + + PolymorphicSingleAttribute is a subclass of SingleAttribute and handles polymorphic + to-one relationships. + + @extends SC.SingleAttribute + @author Colin Campbell + @since SproutCore 1.0 +*/ +SC.PolymorphicSingleAttribute = SC.SingleAttribute.extend( +/** @scope SC.PolymorphicSingleAttribute.prototype */ { + + /** + Contains information on how to transform from and from the passed attribute values. + This property is required so the attribute knows which type to use for the relationship. + If it is not included, it will default to the first provided type. + + @property {String} + @default null + */ + typeKey: null, + + /** + Provides the ability to map the type names provided by the attribute value into + something usable for record relationships. For example, you may need to store + record names on the server but only with the class name, not the fully qualified + path name (ie. 'Record' vs. 'MyApp.Record'). If the passed records are: + + ['MyApp.Foo', 'MyApp.Bar'] + + then your typeMap may be + + ['Foo', 'Bar'] + + 'Foo' and 'Bar' are what gets saved to the datahash when writing to the server, + and the types are used for the relationship within the application. + + @property {Array of Strings} + @default null + */ + typeMap: null, + + /** + Returns the type, resolved to a class. If the type property is a regular + class, returns the type unchanged. Otherwise attempts to lookup the + type as a property path. + + @property {Object} + */ + typeClass: function() { + var ret = this.get('type'), + l, i, type; + if (SC.isArray(ret)) { + l = ret.get('length'); + for (i=0; i -1) { + type = types[idx]; + } else { + SC.Logger.warn("Polymorphic map on property %@ for %@ did not exist on %@".fmt(key, type, record.constructor.toString())); + } + } + + if (types.indexOf(type) > -1) { + if (SC.typeOf(type) === SC.T_STRING) type = SC.objectForPropertyPath(type); + transform = this.transform(type); + if (transform && transform.to) { + value = transform.to(value, this, type, record, key); + } + } else { + SC.Logger.warn("%@ is not a type on %@ of %@".fmt(type.toString(), key, record.constructor.toString())); + } + + return value; + }, + + /** + Converts the passed value from the core attribute value. Uses the polymorphism + information provided to determine the correct type, and sets the typeKey attribute + on the record. + + @param {SC.Record} record the record instance + @param {String} key the key used to access this attribute on the record + @param {Object} value the property value + @returns {Object} attribute value + */ + fromType: function(record, key, value) { + var types = this.get('type'), + type = value.constructor, + typeString = type.toString(), + typeKey = this.get('typeKey'), + typeMap = this.get('typeMap'), + transform, idx; + + if (!SC.empty(typeString)) { + if (typeMap) { + idx = types.indexOf(typeString); + if (idx > -1) { + typeString = typeMap[idx]; + } else { + SC.Logger.warn("Polymorphic map on property %@ for %@ did not exist on %@".fmt(key, typeString, record.constructor.toString())); + } + } + + if (typeKey) { + record.set(typeKey, typeString); + } + } else { + SC.Logger.warn("Could not determine type of %@ for polymorphic relation %@ on %@".fmt(value, key, record)); + type = types.get('firstObject'); + if (SC.typeOf(type) === SC.T_STRING) type = SC.objectForPropertyPath(type); + } + + transform = this.transform(type); + + if (transform && transform.from) { + value = transform.from(value, this, type, record, key); + } + return value; + } + +}); diff --git a/frameworks/datastore/models/record.js b/frameworks/datastore/models/record.js index f35d49479..0147ea1c6 100644 --- a/frameworks/datastore/models/record.js +++ b/frameworks/datastore/models/record.js @@ -1093,6 +1093,19 @@ SC.Record.mixin( /** @scope SC.Record */ { return attr; }, + /** + Returns a SC.PolymorphicSingleAttribute that converts the underlying ID to + a number of types, dependent on a attribute on the record (the name of this + attribute is provided by the typeKey property). + + @param {Array} recordTypes the array of record types the object could be + @param {Hash} opts additional options + @returns {SC.PolymorphicSingleAttribute} created instance + */ + toOneOf: function(recordTypes, opts) { + return SC.PolymorphicSingleAttribute.attr(recordTypes, opts); + }, + /** Returns all storeKeys mapped by Id for this record type. This method is used mostly by the SC.Store and the Record to coordinate. You will rarely diff --git a/frameworks/datastore/tests/models/polymorphic/single.js b/frameworks/datastore/tests/models/polymorphic/single.js new file mode 100644 index 000000000..0d9404864 --- /dev/null +++ b/frameworks/datastore/tests/models/polymorphic/single.js @@ -0,0 +1,124 @@ +// ========================================================================== +// Project: SproutCore - JavaScript Application Framework +// Copyright: ©2006-2009 Apple Inc. and contributors. +// License: Licensed under MIT license (see license.js) +// ========================================================================== +/*globals module ok equals same test MyApp */ + +var MyApp, foo1, foo2, bar1, bar2, snafu1, snafu2; + +module("SC.Polymorphic relationship", { + setup: function() { + MyApp = window.MyApp = SC.Object.create({ + store: SC.Store.create() + }); + + MyApp.Foo = SC.Record.extend({ + // simple case + simplePoly: SC.Record.toOneOf(['MyApp.Bar', 'MyApp.Snafu'], {typeKey:'simplePolyType'}), + simplePolyType: SC.Record.attr(String), + + // mapped + mappedPoly: SC.Record.toOneOf(['MyApp.Bar', 'MyApp.Snafu'], {typeMap:['Bar', 'Snafu'], typeKey:'mappedPolyType'}), + mappedPolyType: SC.Record.attr(String) + }); + + MyApp.Bar = SC.Record.extend(); + MyApp.Snafu = SC.Record.extend(); + + // need to add classnames manually because SC._object_className in frameworks/runtime/core.js + // won't find the objects correctly + MyApp.Foo._object_className = "MyApp.Foo"; + MyApp.Bar._object_className = "MyApp.Bar"; + MyApp.Snafu._object_className = "MyApp.Snafu"; + + SC.RunLoop.begin(); + MyApp.store.loadRecords(MyApp.Foo, [ + { + guid: 'foo1', + simplePolyType: 'MyApp.Bar', + mappedPolyType: 'Bar', + simplePoly: 1, + mappedPoly: 1 + },{ + guid: 'foo2', + simplePolyType: 'MyApp.Snafu', + mappedPolyType: 'Snafu', + simplePoly: 1, + mappedPoly: 1 + } + ]); + + MyApp.store.loadRecords(MyApp.Bar, [ + { + guid: 1 + },{ + guid: 2 + } + ]); + + + MyApp.store.loadRecords(MyApp.Snafu, [ + { + guid: 1 + },{ + guid: 2 + } + ]); + + SC.RunLoop.end(); + + foo1 = MyApp.store.find(MyApp.Foo, 'foo1'); + foo2 = MyApp.store.find(MyApp.Foo, 'foo2'); + + bar1 = MyApp.store.find(MyApp.Bar, 1); + bar2 = MyApp.store.find(MyApp.Bar, 2); + + snafu1 = MyApp.store.find(MyApp.Snafu, 1); + snafu2 = MyApp.store.find(MyApp.Snafu, 2); + }, + + teardown: function() { + MyApp = window.MyApp = foo1 = foo2 = bar1 = bar2 = snafu1 = snafu2 = null; + } +}); + +// .......................................................... +// READING +// + +test("polymorphic with key", function() { + equals(foo1.get('simplePoly'), bar1); + equals(foo2.get('simplePoly'), snafu1); +}); + +test("polymorphic with key, map", function() { + equals(foo1.get('mappedPoly'), bar1, "type attribute mapped to record type"); + equals(foo2.get('mappedPoly'), snafu1, "type attribute mapped to different record type"); +}); + +// .......................................................... +// WRITING +// + +test("polymorphic writing - simple case", function() { + foo1.set('simplePoly', bar2); + equals(foo1.get('simplePoly'), bar2, "setting polymorphic attribute to different record of same recordtype"); + equals(foo1.get('simplePolyType'), 'MyApp.Bar', "polymorphic type attribute matches record"); + + foo1.set('simplePoly', snafu1); + equals(foo1.get('simplePoly'), snafu1, "setting polymorphic attribute to different recordtype"); + equals(foo1.get('simplePolyType'), 'MyApp.Snafu', "polymorphic type attribute matches different record type"); + +}); + +test("polymorphic writing - mapped case", function() { + foo1.set('mappedPoly', bar2); + equals(foo1.get('mappedPoly'), bar2, "setting polymorphic attribute to different record of same recordtype"); + equals(foo1.get('mappedPolyType'), 'Bar', "polymorphic type attribute matches record"); + + foo1.set('mappedPoly', snafu1); + equals(foo1.get('mappedPoly'), snafu1, "setting polymorphic attribute to different recordtype"); + equals(foo1.get('mappedPolyType'), 'Snafu', "polymorphic type attribute matches different record type"); + +}); \ No newline at end of file From 3c80dc120fc20b17d2442834837bbcc67f6ea252 Mon Sep 17 00:00:00 2001 From: Colin Campbell Date: Fri, 13 Aug 2010 19:07:26 -0230 Subject: [PATCH 10/15] ScrollView now detects Safari 5 and changes to delta (was scrolling far too fast). -- fix courtesy stillmotion --- frameworks/desktop/views/scroll.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frameworks/desktop/views/scroll.js b/frameworks/desktop/views/scroll.js index d315a559d..acd848348 100644 --- a/frameworks/desktop/views/scroll.js +++ b/frameworks/desktop/views/scroll.js @@ -762,7 +762,7 @@ SC.ScrollView = SC.View.extend(SC.Border, { // save adjustment and then invoke the actual scroll code later. This will // keep the view feeling smooth. mouseWheel: function(evt) { - var deltaAdjust = (SC.browser.safari && SC.browser.version > 533.0) ? 120 : 1; + var deltaAdjust = (SC.browser.safari && parseInt(SC.browser.version, 10) >= 533.0) ? 120 : 1; this._scroll_wheelDeltaX += evt.wheelDeltaX / deltaAdjust; this._scroll_wheelDeltaY += evt.wheelDeltaY / deltaAdjust; From 5f5e6b824e51ea706669126355ca6f598673c027 Mon Sep 17 00:00:00 2001 From: Colin Campbell Date: Thu, 19 Aug 2010 12:39:55 -0230 Subject: [PATCH 11/15] Created a renderer for SC.Toolbar --- frameworks/desktop/renderers/toolbar.js | 20 ++++++++++++++++++++ frameworks/desktop/views/toolbar.js | 8 +++++++- 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 frameworks/desktop/renderers/toolbar.js diff --git a/frameworks/desktop/renderers/toolbar.js b/frameworks/desktop/renderers/toolbar.js new file mode 100644 index 000000000..6eb0616c1 --- /dev/null +++ b/frameworks/desktop/renderers/toolbar.js @@ -0,0 +1,20 @@ +// ========================================================================== +// Project: SproutCore - JavaScript Application Framework +// Copyright: ©2006-2009 Sprout Systems, Inc. and contributors. +// Portions ©2008-2009 Apple Inc. All rights reserved. +// License: Licened under MIT license (see license.js) +// ========================================================================== + +/** @class + @extends SC.Renderer + @since SproutCore 1.1 +*/ +SC.BaseTheme.renderers.Toolbar = SC.Renderer.extend({ + render: function(context) { + if (this.contentProvider) this.contentProvider.renderContent(context); + }, + + update: function() {} +}); + +SC.BaseTheme.renderers.toolbar = SC.BaseTheme.renderers.Toolbar.create(); \ No newline at end of file diff --git a/frameworks/desktop/views/toolbar.js b/frameworks/desktop/views/toolbar.js index 3428c49da..4a3b28b07 100644 --- a/frameworks/desktop/views/toolbar.js +++ b/frameworks/desktop/views/toolbar.js @@ -43,7 +43,13 @@ SC.ToolbarView = SC.View.extend( this.layout = SC.merge(this.layout, this.anchorLocation); } sc_super(); - } + }, + + createRenderer: function(theme) { + return theme.toolbar(); + }, + + updateRenderer: function(renderer) {} }); From 10476fa60768e7a3ceebfb18619acdf82de2aa4b Mon Sep 17 00:00:00 2001 From: Colin Campbell Date: Fri, 20 Aug 2010 00:15:04 -0230 Subject: [PATCH 12/15] SC.ScrollerView's thumbs now default to their position -- solves an issue where going back to a view that had already been scrolled did not display its correct position --- frameworks/desktop/views/scroller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frameworks/desktop/views/scroller.js b/frameworks/desktop/views/scroller.js index 26be86f48..df7af3c3f 100644 --- a/frameworks/desktop/views/scroller.js +++ b/frameworks/desktop/views/scroller.js @@ -231,7 +231,7 @@ SC.ScrollerView = SC.View.extend( context.push('
', '
', buttons, - '
', + '
', '
', '
', '
'); @@ -240,7 +240,7 @@ SC.ScrollerView = SC.View.extend( context.push('
', '
', buttons, - '
', + '
', '
', '
', '
'); From 41c6d1a49feae8c01d37ee56bb255374bc2732e8 Mon Sep 17 00:00:00 2001 From: Colin Campbell Date: Fri, 20 Aug 2010 10:09:11 -0230 Subject: [PATCH 13/15] Fixed issue with SC.ScrollerView not properly updating its element's class names, e.g., if controlsHidden changed to false, the controls-hidden class would not be removed --- frameworks/desktop/views/scroller.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frameworks/desktop/views/scroller.js b/frameworks/desktop/views/scroller.js index df7af3c3f..944d36ac0 100644 --- a/frameworks/desktop/views/scroller.js +++ b/frameworks/desktop/views/scroller.js @@ -189,7 +189,7 @@ SC.ScrollerView = SC.View.extend( @private */ render: function(context, firstTime) { - var classNames = [], + var classNames = {}, buttons = '', thumbPosition, thumbLength, thumbCenterLength, thumbElement, value, max, scrollerLength, length, pct; @@ -198,21 +198,21 @@ SC.ScrollerView = SC.View.extend( // style them differently using CSS. switch (this.get('layoutDirection')) { case SC.LAYOUT_VERTICAL: - classNames.push('sc-vertical'); + classNames['sc-vertical'] = YES; break; case SC.LAYOUT_HORIZONTAL: - classNames.push('sc-horizontal'); + classNames['sc-horizontal'] = YES; break; } // The appearance of the scroller changes if disabled - if (!this.get('isEnabled')) classNames.push('disabled'); + classNames['disabled'] = !this.get('isEnabled'); // Whether to hide the thumb and buttons - if (this.get('controlsHidden')) classNames.push('controls-hidden'); + classNames['controls-hidden'] = this.get('controlsHidden'); // Change the class names of the DOM element all at once to improve // performance - context.addClass(classNames); + context.setClass(classNames); // Calculate the position and size of the thumb thumbLength = this.get('thumbLength'); From 3cc5fbad31e0eb35677f8b9e4bd6f4cb6bcbdad6 Mon Sep 17 00:00:00 2001 From: Colin Campbell Date: Fri, 20 Aug 2010 23:57:04 -0230 Subject: [PATCH 14/15] Make SC.View#themed resilient to themed value not being present on renderer --- frameworks/foundation/views/view.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frameworks/foundation/views/view.js b/frameworks/foundation/views/view.js index 245a92509..0789ebfb9 100644 --- a/frameworks/foundation/views/view.js +++ b/frameworks/foundation/views/view.js @@ -334,8 +334,11 @@ SC.View = SC.Responder.extend(SC.DelegateSupport, themed: function(property) { var val = this.get(property); if (val === SC.FROM_THEME) { - if (this.renderer) return this.renderer[property]; - else return this.get(property + "Default"); + if (this.renderer) { + val = this.renderer[property]; + if (val !== undefined) return val; + } + return this.get(property + "Default"); } return val; }, From a9dac180550b31e209954e943f875f28abc0e729 Mon Sep 17 00:00:00 2001 From: Colin Campbell Date: Fri, 20 Aug 2010 23:58:26 -0230 Subject: [PATCH 15/15] SC.WellView now uses themed value for contentLayout --- frameworks/desktop/renderers/well.js | 7 +++++++ frameworks/desktop/views/well.js | 14 +++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/frameworks/desktop/renderers/well.js b/frameworks/desktop/renderers/well.js index 43cc7b996..b22161f3c 100644 --- a/frameworks/desktop/renderers/well.js +++ b/frameworks/desktop/renderers/well.js @@ -10,6 +10,13 @@ @since SproutCore 1.1 */ SC.BaseTheme.renderers.Well = SC.Renderer.extend({ + + // leave it set to this constant for backwards compatibility + contentLayout: { + top: SC.WELL_CONTAINER_PADDING, bottom: SC.WELL_CONTAINER_PADDING, + left: SC.WELL_CONTAINER_PADDING, right: SC.WELL_CONTAINER_PADDING + }, + render: function(context) { context.push("
", "
", diff --git a/frameworks/desktop/views/well.js b/frameworks/desktop/views/well.js index 78c4c5e8d..59d50db49 100644 --- a/frameworks/desktop/views/well.js +++ b/frameworks/desktop/views/well.js @@ -6,7 +6,7 @@ // ========================================================================== // Constants -SC.WELL_CONTAINER_PADDING=15; +SC.WELL_CONTAINER_PADDING = 15; /** @class @@ -28,9 +28,11 @@ SC.WellView = SC.ContainerView.extend( Layout for the content of the container view. @property {Object} */ - contentLayout: { - top:SC.WELL_CONTAINER_PADDING, bottom:SC.WELL_CONTAINER_PADDING, - left:SC.WELL_CONTAINER_PADDING, right:SC.WELL_CONTAINER_PADDING}, + contentLayout: SC.FROM_THEME, + contentLayoutDefault: { + top: SC.WELL_CONTAINER_PADDING, bottom: SC.WELL_CONTAINER_PADDING, + left: SC.WELL_CONTAINER_PADDING, right: SC.WELL_CONTAINER_PADDING + }, /** @@ -40,10 +42,12 @@ SC.WellView = SC.ContainerView.extend( createChildViews: function() { // if contentView is defined, then create the content + var contentLayout = this.themed('contentLayout'); + var view = this.get('contentView') ; if (view) { view = this.contentView = this.createChildView(view) ; - view.set('layout', this.contentLayout); + view.set('layout', contentLayout); this.childViews = [view] ; } },