New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Advanced linking / setting of attribute values and computes #646

Closed
justinbmeyer opened this Issue Jan 6, 2014 · 6 comments

Comments

Projects
None yet
4 participants
@justinbmeyer
Contributor

justinbmeyer commented Jan 6, 2014

There is sometimes a need to set a can.Map property value to the value of that compute and keep the can.Map's property up to date with changes within the compute. For example:

var windowManager = new can.Map();

var me = new can.Map({first: "justin", last: "meyer"})

var title = can.compute(function(){
  return "Editing "+ me.attr("last") +", "+ me.attr("first") 
})


windowManager.attr("title", title())

title.bind("change", function(ev, newVal){
  windowManager.attr("title", title())
})

There is a serious problem with the method above: it is not memory safe. The compute should only be bound to if someone is listening to windowState.

can.Component already does much of this, but keeps memory safety by listening to "removed" events.

So, I would like to add an easy ( and safe) way to set this type of relationship up. Here's an example syntax provided by the link method:

var windowManager = new can.Map();

var me = new can.Map({first: "justin", last: "meyer"})

var title = can.compute(function(){
  return "Editing "+ me.attr("last") +", "+ me.attr("first") 
})


windowManager.link("title", title)

Additionally, there's not an easy way to set this up this kind of relationship between parent and child components in CanJS. This is something I would like to tackle after sorting out the low-level functionality works first. But as a quick example:

links: {
  "window-manager title" : function(){
    return "Editing "+ this.attr("last") +", "+ this.attr("first") 
  }
}

Thoughts?

@whitecolor

This comment has been minimized.

Show comment
Hide comment
@whitecolor

whitecolor Jan 7, 2014

Contributor

Yes that is good, I don't know if it is needed to reserve new link method it could be accomplished via .attr("title", title) but maybe it is semantically more correct and safer to bring new method.

"links" look desent.

links: {
    // could just map scope's "name"  attribute to parent's componen't 'title' attribute, 
    // besides "name" could be compute or function defined in scope
    '* title': 'name',

    // besides links could be used for back referencing to parent scopes

    // this would bring window-manager scope to "windowManager" scope property of current component   
    'windowManager': 'window-manager',

      //would link window-manager "prop" to 'windowManagerProp' attribute
     'windowManagerProp': 'window-manager prop' 
}
Contributor

whitecolor commented Jan 7, 2014

Yes that is good, I don't know if it is needed to reserve new link method it could be accomplished via .attr("title", title) but maybe it is semantically more correct and safer to bring new method.

"links" look desent.

links: {
    // could just map scope's "name"  attribute to parent's componen't 'title' attribute, 
    // besides "name" could be compute or function defined in scope
    '* title': 'name',

    // besides links could be used for back referencing to parent scopes

    // this would bring window-manager scope to "windowManager" scope property of current component   
    'windowManager': 'window-manager',

      //would link window-manager "prop" to 'windowManagerProp' attribute
     'windowManagerProp': 'window-manager prop' 
}
@whitecolor

This comment has been minimized.

Show comment
Hide comment
@whitecolor

whitecolor Jan 7, 2014

Contributor

"links" introduce and encourage tight coupling between components but as an option for a certain cases that is very good and quite safe to have such functionality. Not all components are designed to be absolutely independent and reusable.

Contributor

whitecolor commented Jan 7, 2014

"links" introduce and encourage tight coupling between components but as an option for a certain cases that is very good and quite safe to have such functionality. Not all components are designed to be absolutely independent and reusable.

@justinbmeyer

This comment has been minimized.

Show comment
Hide comment
@justinbmeyer

justinbmeyer Jan 7, 2014

Contributor

.attr("prop", compute) currently (and in my opinion) should set the value of prop to the compute. When .attr("prop") is called again, the compute is returned.

Contributor

justinbmeyer commented Jan 7, 2014

.attr("prop", compute) currently (and in my opinion) should set the value of prop to the compute. When .attr("prop") is called again, the compute is returned.

@justinbmeyer

This comment has been minimized.

Show comment
Hide comment
@justinbmeyer

justinbmeyer Jan 7, 2014

Contributor

@whitecolor I'm not sure what

// this would bring window-manager scope to "windowManager" scope property of current component

means.

Also,

'windowManagerProp': 'window-manager prop' 

should be written (I think):

'window-manager prop': 'windowManagerProp': 

The API I'm thinking of is:

@Signature "COMPONENT-TAGNAME SCOPE-ATTRNAME" : linkDefinition

Specifies a value to set on a parent component scope.

@param {String} COMPONENT-TAGNAME A value that specifies the [can.Component.prototype.tag tag] of a parent component or * which will find the first tag that is managed by a component.

@param {String} SCOPE-ATTRNAME The attribute name that will be set on the matched

@param {can.Component.types.linkDefinition} linkDefinition A function or string that defines the value to set on the parent component's scope.

Use

The following has a window-manager component that displays a title set by a name-editor component:

can.Component.extend({
 tag: "window-manager",
 template: "<h1>{{title}}</h1><content/>"
})

can.Component.extend({
 tag: "name-editor",
 template: "<input can-value='first'/> <input can-value='last'/>",
 links: {
   "window-manager title" : function(){
     return "Edit "+this.attr("first")+" "+this.attr("last")
   }
 }
})

var template = can.mustache("<window-manager><name-editor></name-editor></window-manager>")
template( new can.Map({first: "Justin", last: "Meyer"}) )

@typedef {String|function} can.Component.types.linkDefinition

@option {String} If a string, it's assumed to be a property name of the current component's scope.

@option {function(this:can.Map):*} A function that returns the value to set on the parent component's attribute. The this of the function is the
current component's scope.

Contributor

justinbmeyer commented Jan 7, 2014

@whitecolor I'm not sure what

// this would bring window-manager scope to "windowManager" scope property of current component

means.

Also,

'windowManagerProp': 'window-manager prop' 

should be written (I think):

'window-manager prop': 'windowManagerProp': 

The API I'm thinking of is:

@Signature "COMPONENT-TAGNAME SCOPE-ATTRNAME" : linkDefinition

Specifies a value to set on a parent component scope.

@param {String} COMPONENT-TAGNAME A value that specifies the [can.Component.prototype.tag tag] of a parent component or * which will find the first tag that is managed by a component.

@param {String} SCOPE-ATTRNAME The attribute name that will be set on the matched

@param {can.Component.types.linkDefinition} linkDefinition A function or string that defines the value to set on the parent component's scope.

Use

The following has a window-manager component that displays a title set by a name-editor component:

can.Component.extend({
 tag: "window-manager",
 template: "<h1>{{title}}</h1><content/>"
})

can.Component.extend({
 tag: "name-editor",
 template: "<input can-value='first'/> <input can-value='last'/>",
 links: {
   "window-manager title" : function(){
     return "Edit "+this.attr("first")+" "+this.attr("last")
   }
 }
})

var template = can.mustache("<window-manager><name-editor></name-editor></window-manager>")
template( new can.Map({first: "Justin", last: "Meyer"}) )

@typedef {String|function} can.Component.types.linkDefinition

@option {String} If a string, it's assumed to be a property name of the current component's scope.

@option {function(this:can.Map):*} A function that returns the value to set on the parent component's attribute. The this of the function is the
current component's scope.

@whitecolor

This comment has been minimized.

Show comment
Hide comment
@whitecolor

whitecolor Jan 7, 2014

Contributor
  1. I think asterisk should mean in API any direct parent component
links: {
    '* title': 'name',
}

2) and 3) is about back (reverse) referencing, if one wants to take attributes from parent scopes that is why component-tag name on the right (no need in some special characters like ^.

  1. Say one wants to access window-manager parent component's scope, maybe I could do
links: {
    'windowManagerScope': 'window-manager',
}
  1. Say one wants to access some parent component's property (from scope), maybe I could do
links: {
    'windowManagerProp': 'window-manager prop',
}

One could also use astersk to get direct parent

links: {
    'parentProp': '* prop',
}
  1. maybe allow local links between current component attributes and computes (not sure if that is needed)
links: {
    'title': 'titleCompute', // will try to link title attr with titleCompute can.compute
}

it is an equivalent of

scope.link('title', scope.titleCompute)
Contributor

whitecolor commented Jan 7, 2014

  1. I think asterisk should mean in API any direct parent component
links: {
    '* title': 'name',
}

2) and 3) is about back (reverse) referencing, if one wants to take attributes from parent scopes that is why component-tag name on the right (no need in some special characters like ^.

  1. Say one wants to access window-manager parent component's scope, maybe I could do
links: {
    'windowManagerScope': 'window-manager',
}
  1. Say one wants to access some parent component's property (from scope), maybe I could do
links: {
    'windowManagerProp': 'window-manager prop',
}

One could also use astersk to get direct parent

links: {
    'parentProp': '* prop',
}
  1. maybe allow local links between current component attributes and computes (not sure if that is needed)
links: {
    'title': 'titleCompute', // will try to link title attr with titleCompute can.compute
}

it is an equivalent of

scope.link('title', scope.titleCompute)

@ccummings ccummings modified the milestones: 2.2.0, 2.1.0 Apr 25, 2014

@justinbmeyer

This comment has been minimized.

Show comment
Hide comment
@justinbmeyer

justinbmeyer Aug 26, 2014

Contributor

Closing for #1209.

Contributor

justinbmeyer commented Aug 26, 2014

Closing for #1209.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment