Permalink
Browse files

Adds design modes support to SC.Pane, allowing the design of an appli…

…cation to very efficiently and easily change depending on the width of the display.

While a design may be flexible for a wide range of display widths, at a certain point it no longer makes sense to keep stretching or compressing views and instead it is better to completely alter the design.  A prime example is that a design that can flex between a medium sized display like a tablet and a large sized display like a desktop, will not likely work for a small display like a smartphone.  In order to very easily alter the design, SC.Pane provides the designModes property which includes the width thresholds that you want to trigger a change to the overall design of the application.  Each time a threshold width is crossed, the child views will be updated with the new design mode.  At this point, any properties dependent on 'designMode' will recompute allowing you to adjust settings such as isVisible.

As well, SC.View has a new property 'designLayouts' that is simply the layouts for the 'designModes' you define.  For example, if the design mode becomes 'small' any child view that has a 'small' designLayout will have its layout automatically set.

This completes work spec #843.
  • Loading branch information...
1 parent eb42f7f commit 193d98b88bd9c10fcac190ad6141d63b61fd8253 @publickeating publickeating committed Oct 16, 2012
View
197 frameworks/core_foundation/panes/design_mode.js
@@ -0,0 +1,197 @@
+// ==========================================================================
+// Project: SproutCore
+// Copyright: @2012 7x7 Software, Inc.
+// License: Licensed under MIT license (see license.js)
+// ==========================================================================
+sc_require("panes/layout");
+
+
+/** @private This adds design modes support to SC.Pane. */
+SC.Pane.reopen(
+ /** @scope SC.Pane.prototype */ {
+
+ // ------------------------------------------------------------------------
+ // Properties
+ //
+
+ /**
+ A hash of the design modes for this pane and its child views.
+
+ While a design may be flexible enough to stretch up for a large display and
+ compress down for a medium sized display, at a certain point it
+ makes more sense to stop stretching or compressing and implement an
+ additional design specific to the much different display size. In order to
+ make this possible and with as much ease as possible, SC.Pane and SC.View
+ have support for design "modes". A design mode represents a specific
+ design of the app for a range of the display width. You may want to
+ have a "small" design mode for smartphones and a "large" design mode for
+ everything else, but you could even go so far as to have "small-portrait",
+ "small-landscape", "medium-portrait", "medium-landscape", "large-portrait",
+ etc. No matter how many you implement, design modes can very easily be used
+ to reposition, hide or show and modify the styles of your views as needed.
+
+ To use design modes in your pane, set the property to a hash of mode names.
+ The value of each mode represents the upper width limit at which the design
+ mode of the pane should switch. If the width of the window crosses the
+ threshold value, the new design mode will be applied to the pane and each
+ of its child views.
+
+ If the pane or child view has a design mode layout in designLayouts that
+ matches, the layout of the view will be updated. As well, the pane or its
+ child views can make computed properties dependent on designMode to update
+ other properties, such as isVisible or classNames (using classNameBindings).
+
+ For example,
+
+ myPane = SC.PanelPane.create({
+
+ // The pane will support three modes.
+ designModes: {
+ small: 480, // 0 to 480
+ medium: 768, // 481 to 768
+ large: Infinity // 769 to Infinity
+ },
+
+ contentView: SC.View.design({
+
+ // This view will change its layout for small and medium modes.
+ designLayouts: {
+ small: { height: 44 },
+ medium: { width: 180 }
+ },
+
+ // This view will hide itself in large mode.
+ isVisible: function() {
+ return this.get('designMode') !== 'large';
+ }.property('designMode').cacheable()
+
+ })
+
+ }).append();
+
+ > myPane.adjust('width', 480);
+ > myPane.get('designMode');
+ > 'small'
+ > myPane.getPath('contentView.layout');
+ > { height: 44 }
+
+ > myPane.adjust('width', 550);
+ > myPane.get('designMode');
+ > 'medium'
+ > myPane.getPath('contentView.layout');
+ > { width: 180 }
+
+ > myPane.adjust('width', 1024);
+ > myPane.get('designMode');
+ > 'large'
+ > myPane.getPath('contentView.layout');
+ > { width: 180 } // Unchanged because this view doesn't have a 'large' design mode layout
+ > myPane.getPath('contentView.isVisible');
+ > false
+
+ > myPane.adjust('width', 2048);
+ > // Nothing new happens, design mode is already 'large'
+
+ @property {Object|null}
+ @default null
+ */
+ designModes: null,
+
+ // ------------------------------------------------------------------------
+ // Methods
+ //
+
+ /** @private designModes observer */
+ _designModesDidChange: function() {
+ var designModes = this.get('designModes'),
+ designModeNames,
+ designModeWidths;
+
+ designModeNames = this._designModeNames = [];
+ designModeWidths = this._designModeWidths = [];
+
+ // Order the design modes for easier access later.
+ if (designModes) {
+ var key;
+
+ outer:
+ for (key in designModes) {
+ var i, value;
+
+ // Assume that the keys will be ordered smallest to largest so look.
+ value = designModes[key];
+ inner:
+ for (i = designModeWidths.length - 1; i >= 0; i--) {
+ if (designModeWidths[i] < value) {
+ // Exit early!
+ break inner;
+ }
+ }
+
+ i += 1;
+ designModeNames.splice(i, 0, key);
+ designModeWidths.splice(i, 0, value);
+ }
+ }
+
+ // this.invokeOnce(this._checkDesignMode);
+ this.windowSizeDidChange(null, SC.RootResponder.responder.get('currentWindowSize'));
+ },
+
+ /** @private SC.Pane */
+ recomputeDependentProperties: function(original) {
+ original();
+
+ this.addObserver('designModes', this, this._designModesDidChange);
+ this._designModesDidChange();
+ }.enhance(),
+
+ /** @private SC.Pane */
+ remove: function(original) {
+ var ret = original();
+
+ this.removeObserver('designModes', this, this.designModesDidChange);
+
+ return ret;
+ }.enhance(),
+
+ /** @private SC.RootResponder */
+ windowSizeDidChange: function(original, oldSize, newSize) {
+ original();
+
+ var designMode = null,
+ designModeNames = this._designModeNames,
+ designModeWidths = this._designModeWidths,
+ lastDesignMode = this.get('designMode');
+
+ // If no newSize is given set design mode with oldSize (ie. current size)
+ if (SC.none(newSize)) { newSize = oldSize; }
+
+ var i, len;
+ for (i = 0, len = designModeWidths.get('length'); i < len; i++) {
+ var layoutWidthThreshold = designModeWidths.objectAt(i);
+ if (newSize.width < layoutWidthThreshold) {
+ designMode = designModeNames.objectAt(i);
+ break;
+ }
+ }
+
+ // If no smaller designMode was found, use the biggest designMode.
+ if (SC.none(designMode) && designModeNames && designModeNames.get('length') > 0) {
+ designMode = designModeNames.objectAt(i);
+ }
+
+ // Update it if it has changed.
+ if (lastDesignMode !== designMode) {
+ this.set('designMode', designMode);
+
+ //@if(debug)
+ if (!designMode) {
+ // Developer support if they've turned off designMode from previously having it on.
+ SC.warn("Developer Warning: Design modes has been disabled for the pane %@. The layout of the pane and its child views will remain whatever it was for the '%@' design mode.".fmt(this, lastDesignMode));
+ }
+ //@endif
+ }
+ }.enhance()
+
+});
View
400 frameworks/core_foundation/tests/views/pane/design_mode_support.js
@@ -0,0 +1,400 @@
+// ==========================================================================
+// Project: SproutCore
+// Copyright: @2012 7x7 Software, Inc.
+// License: Licensed under MIT license (see license.js)
+// ==========================================================================
+/*globals R, CoreTest, module, test, equals*/
+
+
+var pane, view1, view2, view3, view4, view5;
+
+
+// Localized layout.
+SC.metricsFor('English', {
+ 'Medium.left': 0.25,
+ 'Medium.right': 0.25,
+});
+
+var largeLayout = { centerX: 0, width: 100 },
+ mediumLayout = "Medium".locLayout(),
+ normalLayout = { top: 0, left: 0, bottom: 0, right: 0 },
+ smallLayout = { left: 10, right: 10 };
+
+var DesignModeTestView = SC.View.extend({
+
+ designLayouts: {
+ small: smallLayout,
+ medium: mediumLayout,
+ large: largeLayout
+ },
+
+ init: function() {
+ sc_super();
+
+ // Stub the set method.
+ this.set = CoreTest.stub('setDesignMode', {
+ action: SC.View.prototype.set,
+ expect: function(callCount) {
+ var i, setDesignModeCount = 0;
+
+ for (i = this.history.length - 1; i >= 0; i--) {
+ if (this.history[i][1] === 'designMode') {
+ setDesignModeCount++;
+ }
+ }
+
+ equals(setDesignModeCount, callCount, "set('designMode', ...) should have been called %@ times.".fmt(callCount));
+ }
+ });
+ }
+});
+
+module("SC.View/SC.Pane Design Mode Support", {
+ setup: function() {
+
+ view4 = DesignModeTestView.create({});
+
+ view3 = DesignModeTestView.create({
+ childViews: [view4],
+
+ // Override - no large design layout.
+ designLayouts: {
+ small: smallLayout,
+ medium: "Medium".locLayout()
+ }
+ });
+
+ view2 = DesignModeTestView.create({});
+
+ view1 = DesignModeTestView.create({
+ childViews: [view2, view3]
+ });
+
+ view5 = DesignModeTestView.create({});
+
+ pane = SC.Pane.extend({
+ childViews: [view1]
+ });
+ },
+
+ teardown: function() {
+ if (pane.remove) { pane.remove(); }
+
+ pane = view1 = view2 = view3 = view4 = view5 = null;
+ }
+
+});
+
+
+test("When you append a pane without designModes, it doesn't set designMode on itself or its childViews", function() {
+ pane = pane.create();
+
+ // designMode should not be set
+ view1.set.expect(0);
+ view2.set.expect(0);
+ view3.set.expect(0);
+ view4.set.expect(0);
+
+ pane.append();
+
+ // designMode should not be set
+ view1.set.expect(0);
+ view2.set.expect(0);
+ view3.set.expect(0);
+ view4.set.expect(0);
+
+ equals(view1.get('designMode'), undefined, "designMode should be");
+ equals(view2.get('designMode'), undefined, "designMode should be");
+ equals(view3.get('designMode'), undefined, "designMode should be");
+ equals(view4.get('designMode'), undefined, "designMode should be");
+
+ same(view1.get('layout'), normalLayout, "layout should be");
+ same(view2.get('layout'), normalLayout, "layout should be");
+ same(view3.get('layout'), normalLayout, "layout should be");
+ same(view4.get('layout'), normalLayout, "layout should be");
+});
+
+test("When windowSizeDidChange() is called on a pane without designModes, it doesn't set designMode on itself or its childViews.", function() {
+ var oldSize = SC.RootResponder.responder.get('currentWindowSize'),
+ newSize = oldSize;
+
+ pane = pane.create().append();
+
+ pane.windowSizeDidChange(oldSize, newSize);
+
+ // designMode should not be set
+ view1.set.expect(0);
+ view2.set.expect(0);
+ view3.set.expect(0);
+ view4.set.expect(0);
+
+ equals(view1.get('designMode'), undefined, "designMode should be");
+ equals(view2.get('designMode'), undefined, "designMode should be");
+ equals(view3.get('designMode'), undefined, "designMode should be");
+ equals(view4.get('designMode'), undefined, "designMode should be");
+
+ same(view1.get('layout'), normalLayout, "layout should be");
+ same(view2.get('layout'), normalLayout, "layout should be");
+ same(view3.get('layout'), normalLayout, "layout should be");
+ same(view4.get('layout'), normalLayout, "layout should be");
+});
+
+test("When you add a view to a pane without designModes, it doesn't set designMode on the childView.", function() {
+ pane = pane.create();
+ pane.append();
+ pane.appendChild(view5);
+
+ // adjustDesign() shouldn't be called
+ view5.set.expect(0);
+
+ equals(view5.get('designMode'), undefined, "designMode should be");
+
+ same(view5.get('layout'), normalLayout, "layout should be");
+});
+
+test("When you append a pane with designModes, it sets designMode on itself and its childViews", function() {
+ var oldSize = SC.RootResponder.responder.get('currentWindowSize');
+
+ pane = pane.create({
+ designModes: { small: oldSize.width - 10, large: Infinity }
+ });
+
+ // designMode should not be set
+ view1.set.expect(0);
+ view2.set.expect(0);
+ view3.set.expect(0);
+ view4.set.expect(0);
+
+ pane.append();
+
+ // designMode should be set (for initialization)
+ view1.set.expect(1);
+ view2.set.expect(1);
+ view3.set.expect(1);
+ view4.set.expect(1);
+
+ equals(view1.get('designMode'), 'large', "designMode should be");
+ equals(view2.get('designMode'), 'large', "designMode should be");
+ equals(view3.get('designMode'), 'large', "designMode should be");
+ equals(view4.get('designMode'), 'large', "designMode should be");
+
+ same(view1.get('layout'), largeLayout, "layout should be");
+ same(view2.get('layout'), largeLayout, "layout should be");
+ same(view3.get('layout'), normalLayout, "layout should be");
+ same(view4.get('layout'), largeLayout, "layout should be");
+});
+
+test("When windowSizeDidChange() is called on a pane with designModes, it sets designMode properly on itself and its childViews.", function() {
+ var oldSize = SC.RootResponder.responder.get('currentWindowSize'),
+ newSize = oldSize;
+
+ pane = pane.create({
+ designModes: { small: oldSize.width - 10, large: Infinity }
+ });
+
+ pane = pane.append();
+
+ // designMode should be set (for initialization)
+ view1.set.expect(1);
+ view2.set.expect(1);
+ view3.set.expect(1);
+ view4.set.expect(1);
+
+ equals(view1.get('designMode'), 'large', "designMode should be");
+ equals(view2.get('designMode'), 'large', "designMode should be");
+ equals(view3.get('designMode'), 'large', "designMode should be");
+ equals(view4.get('designMode'), 'large', "designMode should be");
+
+ same(view1.get('layout'), largeLayout, "layout should be");
+ same(view2.get('layout'), largeLayout, "layout should be");
+ same(view3.get('layout'), normalLayout, "layout should be");
+ same(view4.get('layout'), largeLayout, "layout should be");
+
+ newSize.width -= 5;
+ pane.windowSizeDidChange(oldSize, newSize);
+ oldSize = newSize;
+
+ // designMode shouldn't be set again (didn't cross threshold)
+ view1.set.expect(1);
+ view2.set.expect(1);
+ view3.set.expect(1);
+ view4.set.expect(1);
+
+ newSize.width -= 6;
+ pane.windowSizeDidChange(oldSize, newSize);
+
+ // designMode should be set (crossed threshold)
+ view1.set.expect(2);
+ view2.set.expect(2);
+ view3.set.expect(2);
+ view4.set.expect(2);
+
+ equals(view1.get('designMode'), 'small', "designMode should be");
+ equals(view2.get('designMode'), 'small', "designMode should be");
+ equals(view3.get('designMode'), 'small', "designMode should be");
+ equals(view4.get('designMode'), 'small', "designMode should be");
+
+ same(view1.get('layout'), smallLayout, "layout should be");
+ same(view2.get('layout'), smallLayout, "layout should be");
+ same(view3.get('layout'), smallLayout, "layout should be");
+ same(view4.get('layout'), smallLayout, "layout should be");
+});
+
+test("When you add a view to a pane with designModes, it sets designMode on the childView.", function() {
+ var oldSize = SC.RootResponder.responder.get('currentWindowSize');
+
+ pane = pane.create({
+ designModes: { small: oldSize.width - 10, large: Infinity }
+ });
+ pane.append();
+ pane.appendChild(view5);
+
+ // designMode should be set
+ view5.set.expect(1);
+ equals(view5.get('designMode'), 'large', "designMode should be");
+});
+
+test("When you set designModes on a pane without designModes, it sets designMode on the pane and its childViews.", function() {
+ var oldSize = SC.RootResponder.responder.get('currentWindowSize');
+
+ pane = pane.create();
+ pane.append();
+
+ // designMode should not be set
+ view1.set.expect(0);
+ view2.set.expect(0);
+ view3.set.expect(0);
+ view4.set.expect(0);
+
+ SC.run(function() {
+ pane.set('designModes', { small: oldSize.width - 10, large: Infinity });
+ });
+
+ // designMode should be set (for initialization)
+ view1.set.expect(1);
+ view2.set.expect(1);
+ view3.set.expect(1);
+ view4.set.expect(1);
+
+ equals(view1.get('designMode'), 'large', "designMode should be");
+ equals(view2.get('designMode'), 'large', "designMode should be");
+ equals(view3.get('designMode'), 'large', "designMode should be");
+ equals(view4.get('designMode'), 'large', "designMode should be");
+
+ same(view1.get('layout'), largeLayout, "layout should be");
+ same(view2.get('layout'), largeLayout, "layout should be");
+ same(view3.get('layout'), normalLayout, "layout should be");
+ same(view4.get('layout'), largeLayout, "layout should be");
+});
+
+test("When you change designModes on a pane with designModes, it sets designMode on the pane and its childViews if the design mode has changed.", function() {
+ var oldSize = SC.RootResponder.responder.get('currentWindowSize');
+
+ pane = pane.create({
+ designModes: { small: oldSize.width - 10, large: Infinity }
+ });
+ pane.append();
+
+ // designMode should be set (for initialization)
+ view1.set.expect(1);
+ view2.set.expect(1);
+ view3.set.expect(1);
+ view4.set.expect(1);
+
+ equals(view1.get('designMode'), 'large', "designMode should be");
+ equals(view2.get('designMode'), 'large', "designMode should be");
+ equals(view3.get('designMode'), 'large', "designMode should be");
+ equals(view4.get('designMode'), 'large', "designMode should be");
+
+ same(view1.get('layout'), largeLayout, "layout should be");
+ same(view2.get('layout'), largeLayout, "layout should be");
+ same(view3.get('layout'), normalLayout, "layout should be");
+ same(view4.get('layout'), largeLayout, "layout should be");
+
+ // Change the small threshold
+ SC.run(function() {
+ pane.set('designModes', { small: oldSize.width + 10, large: Infinity });
+ });
+
+ // designMode should be set
+ view1.set.expect(2);
+ view2.set.expect(2);
+ view3.set.expect(2);
+ view4.set.expect(2);
+
+ equals(view1.get('designMode'), 'small', "designMode should be");
+ equals(view2.get('designMode'), 'small', "designMode should be");
+ equals(view3.get('designMode'), 'small', "designMode should be");
+ equals(view4.get('designMode'), 'small', "designMode should be");
+
+ same(view1.get('layout'), smallLayout, "layout should be");
+ same(view2.get('layout'), smallLayout, "layout should be");
+ same(view3.get('layout'), smallLayout, "layout should be");
+ same(view4.get('layout'), smallLayout, "layout should be");
+
+ SC.run(function() {
+ pane.set('designModes', { small: oldSize.width - 10, medium: oldSize.width + 10, large: Infinity });
+ });
+
+ // designMode should be set
+ view1.set.expect(3);
+ view2.set.expect(3);
+ view3.set.expect(3);
+ view4.set.expect(3);
+
+ equals(view1.get('designMode'), 'medium', "designMode should be");
+ equals(view2.get('designMode'), 'medium', "designMode should be");
+ equals(view3.get('designMode'), 'medium', "designMode should be");
+ equals(view4.get('designMode'), 'medium', "designMode should be");
+
+ same(view1.get('layout'), mediumLayout, "layout should be");
+ same(view2.get('layout'), mediumLayout, "layout should be");
+ same(view3.get('layout'), mediumLayout, "layout should be");
+ same(view4.get('layout'), mediumLayout, "layout should be");
+});
+
+test("When you unset designModes on a pane with designModes, it clears designMode on the pane and its childViews.", function() {
+ var oldSize = SC.RootResponder.responder.get('currentWindowSize');
+
+ pane = pane.create({
+ designModes: { small: oldSize.width - 10, large: Infinity }
+ });
+ pane.append();
+
+ // designMode should be set (for initialization)
+ view1.set.expect(1);
+ view2.set.expect(1);
+ view3.set.expect(1);
+ view4.set.expect(1);
+
+ equals(view1.get('designMode'), 'large', "designMode should be");
+ equals(view2.get('designMode'), 'large', "designMode should be");
+ equals(view3.get('designMode'), 'large', "designMode should be");
+ equals(view4.get('designMode'), 'large', "designMode should be");
+
+ same(view1.get('layout'), largeLayout, "layout should be");
+ same(view2.get('layout'), largeLayout, "layout should be");
+ same(view3.get('layout'), normalLayout, "layout should be");
+ same(view4.get('layout'), largeLayout, "layout should be");
+
+ // Unset designModes
+ SC.run(function() {
+ pane.set('designModes', null);
+ });
+
+ // designMode should be set
+ view1.set.expect(2);
+ view2.set.expect(2);
+ view3.set.expect(2);
+ view4.set.expect(2);
+
+ equals(view1.get('designMode'), null, "designMode should be");
+ equals(view2.get('designMode'), null, "designMode should be");
+ equals(view3.get('designMode'), null, "designMode should be");
+ equals(view4.get('designMode'), null, "designMode should be");
+
+ same(view1.get('layout'), largeLayout, "layout should be");
+ same(view2.get('layout'), largeLayout, "layout should be");
+ same(view3.get('layout'), normalLayout, "layout should be");
+ same(view4.get('layout'), largeLayout, "layout should be");
+});
View
91 frameworks/core_foundation/views/view/design_mode.js
@@ -0,0 +1,91 @@
+// ==========================================================================
+// Project: SproutCore
+// Copyright: @2012 7x7 Software, Inc.
+// License: Licensed under MIT license (see license.js)
+// ==========================================================================
+sc_require("views/view");
+
+
+/** @private This adds design modes support to SC.View. */
+SC.View.reopen(
+ /** @scope SC.View.prototype */ {
+
+ // ------------------------------------------------------------------------
+ // Properties
+ //
+
+ /**
+ The current design mode of the view.
+
+ If the pane that this view belongs to has designModes specified, this
+ property will be set automatically when the view is created and as the
+ window size changes.
+
+ Note that setting the design mode also updates all child views of this
+ view.
+
+ @property {String}
+ @default null
+ */
+ designMode: function(key, value) {
+ var designLayouts,
+ newLayout;
+
+ if (value !== undefined) {
+ // If the view has a designModeLayout, adjust its layout to match.
+ designLayouts = this.get('designLayouts');
+ if (designLayouts) {
+ newLayout = designLayouts[value] || null;
+
+ if (newLayout) {
+ this.set('layout', newLayout);
+ }
+ //@if(debug)
+ else if (value) {
+ // Developer support if they've implemented designLayouts but maybe missed a layout for this mode.
+ SC.warn("Developer Warning: The view %@ has designLayouts, but none matching the current designMode: '%@'".fmt(this, value));
+ }
+ //@endif
+ }
+
+ // Set the designMode on each child view (may be null).
+ this.adjustChildDesignModes(value);
+ } else {
+ value = null;
+ }
+
+ return value;
+ }.property().cacheable(),
+
+ /**
+ The dynamic layouts for this view depending on the current design mode.
+
+ If you specify designModes on the view's pane, this hash will be checked
+ for a matching design mode layout to set for the current design mode.
+
+ For example, if the pane has designModes 'small' and 'large', you could
+ specify designLayouts 'small' and 'large' that would be used for
+ the matching design mode.
+
+ @property {Object|null}
+ @default null
+ */
+ designLayouts: null,
+
+ // ------------------------------------------------------------------------
+ // Methods
+ //
+
+ /** @private Recursively set the designMode on each child view. */
+ adjustChildDesignModes: function (designMode) {
+ var childViews = this.get('childViews');
+
+ var i, len = childViews.get('length');
+ for (i = 0; i < len; i++) {
+ var childView = childViews.objectAt(i);
+
+ childView.set('designMode', designMode);
+ }
+ }
+
+});
View
6 frameworks/core_foundation/views/view/manipulation.js
@@ -126,6 +126,10 @@ SC.View.reopen(
if(view.parentViewDidChange) view.parentViewDidChange();
if(view.layoutDidChange) view.layoutDidChange();
+ // Pass the current designMode to the view (and its children).
+ var designMode = this.get('designMode');
+ if (designMode) { view.set('designMode', designMode); }
+
view.endPropertyChanges();
// Make sure all notifications are delayed since the appending
@@ -139,7 +143,7 @@ SC.View.reopen(
}
});
- // Even though its layer has not necessarily been created, the child views
+ // Even though its layer has not necessarily been created, the child views
// are added immediately. Hence notify views immediately.
if (this.didAddChild) { this.didAddChild(view, beforeView) ; }
if (view.didAddToParent) { view.didAddToParent(this, beforeView) ; }
View
103 frameworks/desktop/tests/views/collection/itemViewForContentIndex.js
@@ -9,30 +9,30 @@ var view, del, content ;
module("SC.CollectionView.itemViewForContentIndex", {
setup: function() {
- content = "a b c".w().map(function(x) {
+ content = "a b c".w().map(function(x) {
return SC.Object.create({ title: x });
});
-
+
del = {
fixture: {
isEnabled: YES,
isSelected: YES,
outlineLevel: 3,
disclosureState: SC.LEAF_NODE
},
-
- contentIndexIsEnabled: function() {
- return this.fixture.isEnabled;
+
+ contentIndexIsEnabled: function() {
+ return this.fixture.isEnabled;
},
-
- contentIndexIsSelected: function() {
- return this.fixture.isSelected;
+
+ contentIndexIsSelected: function() {
+ return this.fixture.isSelected;
},
- contentIndexOutlineLevel: function() {
- return this.fixture.outlineLevel;
+ contentIndexOutlineLevel: function() {
+ return this.fixture.outlineLevel;
},
-
+
contentIndexDisclosureState: function() {
return this.fixture.disclosureState ;
}
@@ -41,37 +41,37 @@ module("SC.CollectionView.itemViewForContentIndex", {
// NOTE: delegate methods above are added here.
view = SC.CollectionView.create(del, {
content: content,
-
+
layoutForContentIndex: function(contentIndex) {
return this.fixtureLayout ;
},
-
+
fixtureLayout: { left: 0, right: 0, top:0, bottom: 0 },
-
+
groupExampleView: SC.View.extend(), // custom for testing
-
+
exampleView: SC.View.extend(), // custom for testing
-
+
testAsGroup: NO,
-
+
contentIndexIsGroup: function() { return this.testAsGroup; },
-
+
contentGroupIndexes: function() {
if (this.testAsGroup) {
return SC.IndexSet.create(0, this.get('length'));
} else return null ;
},
-
+
fixtureNowShowing: SC.IndexSet.create(0,3),
computeNowShowing: function() {
return this.fixtureNowShowing;
}
-
+
});
-
+
// add in delegate mixin
del = SC.mixin({}, SC.CollectionContent, del);
-
+
}
});
@@ -85,27 +85,27 @@ function shouldMatchFixture(itemView, fixture) {
test("creating basic item view", function() {
var itemView = view.itemViewForContentIndex(1);
-
+
// should use exampleView
ok(itemView, 'should return itemView');
ok(itemView.kindOf(view.exampleView), 'itemView %@ should be kindOf %@'.fmt(itemView, view.exampleView));
-
+
// set added properties
equals(itemView.get('content'), content.objectAt(1), 'itemView.content should be set to content item');
equals(itemView.get('contentIndex'), 1, 'itemView.contentIndex should be set');
equals(itemView.get('owner'), view, 'itemView.owner should be collection view');
equals(itemView.get('displayDelegate'), view, 'itemView.displayDelegate should be collection view');
equals(itemView.get('parentView'), view, 'itemView.parentView should be collection view');
-
+
// test data from delegate
shouldMatchFixture(itemView, view.fixture);
});
test("returning item from cache", function() {
-
+
var itemView1 = view.itemViewForContentIndex(1);
ok(itemView1, 'precond - first call returns an item view');
-
+
var itemView2 = view.itemViewForContentIndex(1);
equals(itemView2, itemView1, 'retrieving multiple times should same instance');
@@ -115,12 +115,29 @@ test("returning item from cache", function() {
var itemView4 = view.itemViewForContentIndex(1, NO);
equals(itemView4, itemView3, 'itemViewForContentIndex(1) [no reload] should return newly cached item after recache');
-
+
+});
+
+// Tests support for the addition of designModes to SC.Pane and SC.View. Since
+// SC.CollectionView doesn't use child views and thus doesn't call
+// SC.View:insertBefore, it needs to pass the designMode down to its item views
+// itself.
+test("set designMode on item views", function() {
+ var itemView;
+
+ // Initial designMode before creating the item view.
+ view.set('designMode', 'small');
+ itemView = view.itemViewForContentIndex(1);
+ equals(itemView.get('designMode'), 'small', "itemView.designMode should be set to match the current value of the collection");
+
+ // Changes to designMode after creating the item view.
+ view.set('designMode', 'large');
+ equals(itemView.get('designMode'), 'large', "itemView.designMode should be set to match the current value of the collection");
});
// ..........................................................
// ALTERNATE WAYS TO GET AN EXAMPLE VIEW
-//
+//
test("contentExampleViewKey is set and content has property", function() {
var CustomView = SC.View.extend();
@@ -156,7 +173,7 @@ test("contentExampleViewKey is set and content property is empty", function() {
// ..........................................................
// GROUP EXAMPLE VIEW
-//
+//
test("delegate says content is group", function() {
view.testAsGroup = YES ;
@@ -168,7 +185,7 @@ test("delegate says content is group", function() {
test("contentGroupExampleViewKey is set and content has property", function() {
view.testAsGroup = YES ;
-
+
var CustomView = SC.View.extend();
var obj = content.objectAt(1);
obj.set('foo', CustomView);
@@ -209,25 +226,25 @@ test("contentGroupExampleViewKey is set and content property is empty", function
test("_contentGroupIndexes's cache should be properly invalidated", function() {
view.testAsGroup = YES;
-
+
// force setup of range observers
view.updateContentRangeObserver();
-
+
ok(view.get('_contentGroupIndexes').isEqual(SC.IndexSet.create(0, 3)), "contentGroupIndexes should have correct initial value");
-
+
view.get('content').removeAt(2, 1);
ok(view.get('_contentGroupIndexes').isEqual(SC.IndexSet.create(0, 2)), "contentGroupIndexes should have updated value after deletion");
});
// ..........................................................
// DELEGATE SUPPORT
-//
+//
test("consults delegate if set", function() {
view.fixture = null; //break to make sure this is not used
view.delegate = del;
-
+
var itemView = view.itemViewForContentIndex(1);
ok(itemView, 'returns item view');
shouldMatchFixture(itemView, del.fixture);
@@ -236,9 +253,9 @@ test("consults delegate if set", function() {
test("consults content if implements mixin and delegate not set", function() {
view.fixture = null; //break to make sure this is not used
view.delegate = null;
-
+
SC.mixin(content, del) ; // add delegate methods to content
-
+
var itemView = view.itemViewForContentIndex(1);
ok(itemView, 'returns item view');
shouldMatchFixture(itemView, content.fixture);
@@ -250,20 +267,20 @@ test("prefers delegate over content if both implement mixin", function() {
view.delegate = del;
SC.mixin(content, del) ; // add delegate methods to content
content.fixture = null ; //break
-
+
var itemView = view.itemViewForContentIndex(1);
ok(itemView, 'returns item view');
shouldMatchFixture(itemView, del.fixture);
});
// ..........................................................
// SPECIAL CASES
-//
+//
test("after making an item visible then invisible again", function() {
view.isVisibleInWindow = YES ;
-
+
// STEP 1- setup with some nowShowing
SC.run(function() {
view.set('fixtureNowShowing', SC.IndexSet.create(1));
@@ -273,7 +290,7 @@ test("after making an item visible then invisible again", function() {
var itemView = view.itemViewForContentIndex(1);
equals(itemView.get('parentView'), view, 'itemView has parent view');
-
+
// STEP 2- setup with NONE visible
SC.run(function() {
view.set('fixtureNowShowing', SC.IndexSet.create());
@@ -294,7 +311,7 @@ test("after making an item visible then invisible again", function() {
itemView = view.itemViewForContentIndex(1);
equals(itemView.get('parentView'), view, 'itemView has parent view');
-
+
});
View
22 frameworks/desktop/views/collection.js
@@ -1107,6 +1107,7 @@ SC.CollectionView = SC.View.extend(SC.CollectionViewDelegate, SC.CollectionConte
// return from cache if possible
var content = this.get('content'),
+ designMode = this.get('designMode'),
item = content.objectAt(idx),
del = this.get('contentDelegate'),
groupIndexes = this.get('_contentGroupIndexes'),
@@ -1199,6 +1200,7 @@ SC.CollectionView = SC.View.extend(SC.CollectionViewDelegate, SC.CollectionConte
var attrs = this._TMP_ATTRS;
attrs.contentIndex = idx;
attrs.content = item;
+ attrs.designMode = designMode;
attrs.owner = attrs.displayDelegate = this;
attrs.parentView = parentView; // Same here; shouldn't be needed
attrs.page = this.page;
@@ -1877,6 +1879,26 @@ SC.CollectionView = SC.View.extend(SC.CollectionViewDelegate, SC.CollectionConte
},
// ..........................................................
+ // DESIGN MODE SUPPORT
+ //
+
+ /** @private Set the designMode on each item view. */
+ adjustChildDesignModes: function (designMode) {
+ sc_super();
+
+ var idx,
+ itemView,
+ nowShowing = this.get('nowShowing');
+
+ // Only loop through the now showing indexes, if the content is sparsely
+ // loaded we could inadvertently trigger reloading unneeded content.
+ nowShowing.forEach(function(idx) {
+ itemView = this.itemViewForContentIndex(idx);
+ itemView.set('designMode', designMode);
+ }, this);
+ },
+
+ // ..........................................................
// KEYBOARD EVENTS
//

0 comments on commit 193d98b

Please sign in to comment.