Polymer- use behavior to share object between elements? - javascript

Is it possible to use behaviors to share a object between elements?
<script>
selectedBehavior = {
properties: {
selected: Object
}
}
</script>
<dom-module id="paper-menu-custom">
<style>
</style>
<template>
<paper-menu attr-for-selected="name" selected="{{selected.choice}}">
...
<script>
Polymer({
is: "paper-menu-custom",
behaviors: [selectedBehavior]
});
toolbars = document.querySelector('paper-menu-custom');
toolbars.selected.choice = "home";
Uncaught TypeError: Cannot set property 'choice' of undefined

You do not need to use a behavior to share information between elements.
You should use IronMeta like so :
Declaratively and with data-binding :
<iron-meta key="my-unique-key" value="{{mySharedInformation}}"></iron-meta>
Then use mySharedInformation the same way you would any custom element's properties. Setting it will update the value of any other <iron-meta> in your code that shares the same key.
In plain javascript :
Read
var mySharedInformation = new Polymer.IronMeta().byKey('my-unique-key');
Write
new Polymer.IronMeta({key: 'my-unique-key', value: mySharedInformation});

Take a look at my object in github (https://github.com/akc42/akc-meta), it allows one element to publish a value with a key, and other ti have multiple instances subscribe to it and get the data out again.
It does it by keeping instances in a private variable
(function(){
var private;
Polymer({element definition has access to private});
})();

I got it with this:
<script>
selectedBehavior = {
properties: {
selected: {
type: Object,
value: function() { return { choice: 'home'} }
}
}
}
</script>
It seems specifying a object is not enough. I need to return a object for the value of object. Doesn't make alot of sense because in documentation I should just be able to say foo: Object. But, maybe this is a special case sense I am using it as a behavior.
Unfortunately, two elements can not share the same obj.property through behaviors. Each will have it's own instance.

If you want to share an object between the different instances of your element, you have to avoid using a function as describe in the Documentation
So this should be working as you expect:
<script>
selectedBehavior = {
properties: {
selected: {
type: Object,
value: { choice: 'home'} }
}
}
}
</script>

Related

Use constant for Ember computed property dependent key

For an Ember app, is it possible to use a constant as part of a computed property key ?
So, essentially, I have a constant as below;
MY_SECTION {
MY_FIELD: "my-field-id"
}
What I want is a computed property on "my-field-id" i.e.
myCP: function() {
console.log('Inside CP...');
}.property('section.field.my-field-id.value')
However, I want to be able to use constant for my-field-id instead of using it directly. Is that possible ?
Ola #testndtv, thanks for your question! Yes it is entirely possible to use a constant in the key for a computed property, but to make use of it you will need to use the more modern syntax that #jelhan was mentioning because .poperty() is deprecated.
Here is a working example of a controller that I have tested locally and is working as you would expect:
import Controller from '#ember/controller';
import { defineProperty, computed } from '#ember/object';
const PROPERTY_ID = 'some-random-string-that-is-too-long-to-write';
export default Controller.extend({
// this is just for the example so we can show the value in the template
// it is not needed to get this to work
PROPERTY_ID: PROPERTY_ID,
init() {
this._super(...arguments);
defineProperty(this, 'myCP', computed(PROPERTY_ID, function() {
return this.get(PROPERTY_ID);
}));
},
actions: {
addOne() {
// this is just for the example to stop the result always being NaN because
// null + 1 = NaN
let value = this.get(PROPERTY_ID) || 0;
this.set(PROPERTY_ID, value + 1);
}
}
});
As you can see we are making use of defineProperty which is being imported from '#ember/object'. You can read more about it in the API documentation
The key insight here is that you need to define the property dynamically in the init() for this Ember object.
The corresponding template for this Controller is as follows:
Property ID is: {{PROPERTY_ID}}
<br>
And the value is: {{get this PROPERTY_ID}}
<br>
<button {{action 'addOne'}}>Add One</button>

changing prototype Elements set scopes

I am wanting to attach into an Elements default property such as innerHTML as a backup under an object that way it does not pollute the Elements properties. so to help give an idea of what I am trying to achieve and what currently works:
Element.prototype._backupPropertyDescriptors = {};
Element.prototype._backupProperties = {};
Element.prototype._backupPropertyDescriptors._innerHTML = Object.getOwnPropertyDescriptor(Element.prototype,'innerHTML');
//This is what I want to do but loses Elements scope:
Object.defineProperty(Element.prototype._backupProperties,'_innerHTML',Element.prototype._backupPropertyDescriptors._innerHTML);
//the scope has changed from element to _backupProperties so this property fails.
//The working version:
Object.defineProperty(Element.prototype,'_innerHTML',Element.prototype._backupPropertyDescriptors._innerHTML);
//the reason for this is I want to be able to manipulate the get and set such as:
Object.defineProperty(Element.prototype._backupProperties,'_innerHTML',{configurable:true,enumerable:true,get:function(){console.log('getting',this.innerHTML);return this.innerHTML},set:function(val){console.log('doing something here before setting');this.innerHTML = val;}});
The problem with this is once it is inside of backup the this statement no longer holds the element...
I know one way to do this would be to use a bind or call but that still poses the how do I get the elements scope... as this during define property is the window..
So for anyone looking to try and do this, here is the solution I found :) might be something better out there, but this does work. requires only 3 properties in the prototype and then all others get put inside a single one.
Element.prototype._backupPropertyDescriptors = {};
Element.prototype._backupProperties = {};
Object.defineProperty(Element.prototype,'_backupProvider',
{
writeable:false,
enumerable:true,
configurable:true,
get:function()
{
var _backupProperties = this._backupProperties;
_backupProperties._Element = this;
return {_Element:this,_backupPropertyDescriptors:this._backupPropertyDescriptors,_backupProperties:_backupProperties};
}
});
//These first ones set up the main provider and property and descriptor holders.
//then just copy a descriptor:
Element.prototype._backupPropertyDescriptors._innerHTML = Object.getOwnPropertyDescriptor(Element.prototype,'innerHTML');
//and assign it to a new property inside the backupProperties:
Object.defineProperty(Element.prototype._backupProvider._backupProperties,'_innerHTML',
{
enumerable:true,
configurable:true,
get:function()
{
return this._Element._backupProvider._backupPropertyDescriptors._innerHTML.get.call(this._Element);
},
set:function(val)
{
console.log('setting html to: ',val);
this._Element._backupProvider._backupPropertyDescriptors._innerHTML.set.call(this._Element,val);
}
});
//and if you wanted to do something really crazy.... like overwrite the original..
Object.defineProperty(Element.prototype,'innerHTML',
{
enumerable:true,
configurable:true,
get:function()
{
return this._backupProvider._backupProperties._innerHTML;
},
set:function(val)
{
console.log('setting html to: ',val);
//do some crazy two way template binding here or something else crazy
this._backupProvider._backupProperties._innerHTML = val;
}
});
that is all.. thanks for the help #Bergi

Ember not updating template when I add or remove an object from an array field in the model

So I've got a model that has a field of an array objects it looks like this
App.Post = DS.Model.extend({
...
codes: attr(),
...
});
and Codes looks like this
codes: [
{
code: stuff
comment: stuff_1
other_things: other_stuff
},
{
...
},
{
...
}
...
]
So now I have an add / remove button which has actions attached to them and this is what they do
add_code_input: function() {
var codes = this.get('model.codes');
var self = this;
var last_code = codes[codes.length-1];
// Cannot edit as an ember.set error is occurring
last_code.code = 'Add new (please change)';
last_code.code_type = "";
last_code.comment = "";
console.log(last_code);
codes.push(last_code);
this.set('model.codes', codes);
console.log(codes);
},
remove_code_input: function() {
var codes = this.get('model.codes');
codes.pop();
console.log(codes);
this.set('model.codes', codes);
}
So the remove works fine but the add doesn't work.
It gives me this error when I try to update last_code: Uncaught Error: Assertion Failed: You must use Ember.set() to access this property (of [object Object])
I essentially want to add a dummy object that user can change.
So first issue is figuring out how to add dummy objects into the array properly and secondly how to update the template as the model changes.
You should be using arr.pushObject(obj) and arr.popObject() for manipulating an array in Ember (think of it as the setter/getter of arrays).
is codes really just attr() because it appears to be behaving like a DS record.
If it is a record, you just use record.set('foo', 'bar') if it's just a POJO you can use Ember.set(obj, 'foo', 'bar').
It should be as easy as this (I'm assuming you're using and in the ObjectController here)
var newCode = {
code:'foo'
};
this.get('codes').pushObject(newCode);

Accessing object literals in Javascript

This code is an example from Marionette:
AppLayout = Backbone.Marionette.Layout.extend(
{
template: "#layout-template",
regions:
{
menu: "#menu",
content: "#content"
}
});
var layout = new AppLayout();
layout.menu.show(new MenuView());
layout.content.show(new MainContentView());
The last two lines confuse me. Why doesn't it read:
layout.regions.menu.show(new MenuView());
layout.regions.content.show(new MainContentView());
Can someone please explain why layout.menu works and layout.regions.menu doesn't?
What if I wanted to access template? Wouldn't that be layout.template? template and regions are at the same depth inside layout.
Here is the constructor function from the marionette code:
// Ensure the regions are avialable when the `initialize` method
// is called.
constructor: function () {
this._firstRender = true;
this.initializeRegions();
var args = Array.prototype.slice.apply(arguments);
Marionette.ItemView.apply(this, args);
},
I believe it was implemented that way because 'layout.menu' is shorter and simpler than 'layout.regions.menu'. Looks like you expected the literal "#menu" to be replaced with a region manager object.
The options you passed in when creating the view, including the template, can be found in layout.options. So in your case layout.options.template should equal '#layout-template', and the regions definition hash would be at layout.options.regions... still the same level.
Unless there is more to the example then you are showing like the Backbone.Marionette.Layout methods, then its not accessing regions.menu like you think it is.
With just the code you have provided the code above is actually creating a menu attribute, which then has a show attribute so your layout object would actually look like this:
layout {
menu : {
show : new MenuView
},
content : {
show : new MainContentView
},
template: "#layout-template",
regions:
{
menu: "#menu",
content: "#content"
}
}
In javascript the (dot) operator can be used to access a property of an attribute or if no property with that name exists then it will create that property.
I'm not familiar with the backbone.js framework but my guess is that they provide for skipping part of the property lookup chain. which means that the above would end up producing this as your layout object:
layout {
template: "#layout-template",
regions:
{
menu : {
show : new MenuView
},
content : {
show : new MainContentView
}
}
}
But again that's just a guess on my part since I don't use backbone.
You can learn more about the object model and how it works with inheritance right here.

jQuery Plugins: If I want to call something like $('selector').plugin.group.method(), how can I achieve this?

I have written some relatively simple jQuery plug-ins, but I am contemplating writing something more advanced in order to keep commonly used methods on the site easily accessible and DRY
For example, I might have something like this for a structure:
plugin
- popup
- element
...
=== popup ===
- login
- product
...
=== element ===
- shoppingCart
- loginStatus
...
So, to bind a popup login popup event, I'd like to be able to do:
$('#login_button').plugin.popup.login();
What's the best way to do this? Is there a better way of achieving what I want to do?
Cheers,
The way farhan Ahmad did it was pretty much right... it just needs deeper levels to suit your needs your implementation would look like this:
jQuery.fn.plugin = function(){
//"community" (global to local methods) vars here.
var selectedObjects = this; //-- save scope so you can use it later
// return the objects so you can call them as necessary
return {
popup: { //plugin.popup
login: function(){ //plugin.popup.login
//selectedObjects contains the original scope
console.log(selectedObjects);
},
product: function(){} //plugin.popup.product
},
element: { //plugin.element
shoppingCart: function() {}, //plugin.element.shoppingCart
loginStatus: function() {} //plugin.element.loginStatus
}
}
}
So now if you call:
$("#someDiv").plugin.login(); the result will be as expected. I hope this helps.
jQuery.fn.messagePlugin = function(){
var selectedObjects = this;
return {
saySomething : function(message){
$(selectedObjects).each(function(){
$(this).html(message);
});
return selectedObjects; // Preserve the jQuery chainability
},
anotherAction : function(){
//...
return selectedObjects;
}
};
}
We use it like this:
$('p').messagePlugin().saySomething('I am a Paragraph').css('color', 'red');
The selected objects are stored in the messagePlugin closure, and that function returns an object that contains the functions associated with the plugin, the in each function you can perform the desired actions to the currently selected objects.

Categories