How do I create a singleton element in Polymer - polymer

I want to create an element that a user can only instantiate once.
So far the best I can think of doing is defining the element within an anonymous function and throwing an error when I find the element already exists. Is there a way so it just rejects being created?
(function(){
var singleton = false;
Polymer({
is:'my-singleton',
created:function(){
if(singleton) {
throw new Error ('only one my-singleton should be created');
}
singleton = this;
}
});
})();

Do you want to restrict the number of <my-singleton> on the page, or just have a singleton 'state'?
To share a singleton state/service between multiple element instances you can do this:
(function() {
'use strict';
// singleton shared connection between all instances of rg-signalr
var singletonState = {
counter: 0
};
Polymer({
is: 'my-element-using-singleton-state',
attached() {
singletonState.counter++;
}
});
})();

I seems that there is an undocumented remove() function for elements. I just moved the code previously to the attached function, and keep a variable which notes if I have been made active or not.
(function(){
var singleton = false;
Polymer({
is:'my-singleton',
attached:function(){
if(singleton) {
this.isActive = false;
this.remove();
} else {
singleton = true;
this.isActive = true;
// remainder of element initialization
}
},
detached:function() {
if(this.isActive) singleton = false;
}
});
})();
This seems to work very well.

Related

Angular5 Bindings not working with function call in object [hidden]

I currently have the hidden attribute of one of my divs binded to a boolean in my typescript. But, when I am changing the value of the boolean in one of my function calls nested within an object the dom is not updating on the front end?
typescript
hideSymbols = true;
bindings = {
enter: {
key: 13,
handler: function() {
console.log('enter pressed');
this.hideSymbols = !this.hideSymbols;
console.log(this.hideSymbols);
}
}
};
html
<div [hidden]="hideSymbols">
<button id="equalsBtn" class="symbolBtn">=</button>
<button id="impliesBtn" class="symbolBtn">=></button>
</div>
It works if I am not making the call in this handler but I need to in order for my ngx-quill instance to update how the enter key works. Essentially, why is hideSymbols getting updated but on my web view the element does not disappear and reappear?
Make that:
handler: () => {...
...rather than use function. A function defined using function has its own this.
I'm not sure this is the whole problem without more context, but it's probably at least part of the problem.
this.hideSymbols = !this.hideSymbols; is executing in the wrong scope.
This is what you have:
var result1 = null;
var exhibitA = {
execute: function(){
this.result1 = "hello";
}
};
exhibitA.execute();
console.log({ exhibitA, result1 });
This is what you want:
var result2 = null;
var exhibitB = {
execute: () => {
this.result2 = "hello";
}
};
exhibitB .execute();
console.log({ exhibitB, result2 });

Behaviors in Polymer ES6 class

How can I use behaviors in Polymer element build with ES6 class ? I was trying to use existing ES5 behavior with newly defiend ES6 element but I'm getting 'error TyperError: this.log is not a function' when I'm calling function defined in behavior.
<!DOCTYPE html>
<html>
<head>
<base href="https://polygit.org">
<script src="/components/webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="/components/polymer/polymer.html">
</head>
<body>
<my-element id="myElement" verbose></my-element>
</body>
<script>
(function() {
'use strict';
// Behavior (ES5)
var myBehaviors = myBehaviors || {};
myBehaviors.VerboseBehavior = {
properties: {
verbose: {
type: Boolean,
value: false
}
},
log: function(msg) {
if (!this.verbose) {
return;
}
if (this.id) {
console.log(this.localName + ' (#' + this.id + '): ' + msg);
} else {
console.log(this.localName + ': ' + msg);
}
}
};
// Element (ES6)
class myElement {
beforeRegister() {
this.is = 'my-element';
this.behaviors = [
myBehaviors.VerboseBehavior
];
this.properties = {
hello: {
type: String,
value: 'world'
}
};
}
ready() {
this.log('ready'); // error TypeError: this.log is not a function
}
attached() {
this.say();
}
say() {
console.log(this.hello);
}
};
Polymer(myElement);
})();
</script>
</html>
Define the behaviors with a getter.
(function() {
'use strict';
let SkeletonPolymerBehavior = {
// Behavior definition
};
class SkeletonPolymer {
// Define behaviors with a getter
get behaviors() {
return [SkeletonPolymerBehavior];
}
// Element setup goes here instead of created() callback
beforeRegister() {}
// Define other lifecycle methods as you need
registered() {}
created() {}
ready() {}
factoryImpl() {}
attached() {}
detached() {}
attributChanged(){}
}
Polymer(SkeletonPolymer);
)();
Note that the answer given by leodido will not work if you use extended behaviors. Extended behaviors in Polymer are arrays of behaviors themselves. I don't know what's going on exactly but apparently Polymer is parsing the protoype of your class and flattening the behavior array, writing the flattened version back to your prototype. The fixed getter function will ignore the flattened version and always return the deep array. That will not do. So you have two options.
You can flatten the array yourself:
get behaviors() {
return [
MyFlatBehavior, MyExtendedBehavior
].reduce(function(a, b) {return a.concat(b);});
}
However, since Polymer may call get behaviors a lot (it does in my case), this will introduce a minor performance penalty. The alternative is letting it set the behaviors:
let SkeletonPolymerBehavior = [MyFlatBehavior, MyExtendedBehavior];
class SkeletonPolymer {
get behaviors( ) {return SkeletonPolymerBehavior;}
set behaviors(b) {SkeletonPolymerBehavior = b;}
...
}
Finally you can flatten the array right away:
let SkeletonPolymerBehavior = [
MyFlatBehavior, MyExtendedBehavior
].reduce(function(a, b) {return a.concat(b);});
class SkeletonPolymer {
get behaviors( ) {return SkeletonPolymerBehavior;}
...
}
Note that my flatten function is not safe! It worked in my case, but it will fail when extending extended behaviors and possibly in other cases. You definitely need something more sophisticated there. If you don't want to bother (and I see no compelling reason to) you should definitely go with the second version, letting Polymer set the behaviors.
Final note: I tried abstracting that away into an es2015 Behaviors mixin. However, Polymer's prototype magic does not catch stuff inherited from base classes and the getters and setters defined there are never called.
However, it is possible to mess with the prototype the es5 way. This utility function will ease the addition of extended behaviors:
function addBehaviors(clas, ...behaviors) {
let bhv = behaviors;
Object.defineProperty(clas.prototype, "behaviors", {
get:function( ) {return bhv;},
set:function(b) {return bhv = b;}
});
}
Usage:
class SkeletonPolymer {...}
addBehaviors(SkeletonPolymer, MyFlatBehavior, MyExtendedBehavior);

Angular: Directive watch model defined in own controller

I try to update my chart when data is pushed to a websocket. I want to do this by defining my own directive in angular, but the binding of data does not work. The Code:
angular.directive("myChart", ['service', function (service) {
function ChartController ($scope) {
var ws = new WebSocket("url/to/websocket/");
var data = service.initData(); // initialize data table
ws.onmessage = function (event) { // listen and update data
data = service.updateData(event.data);
$scope.recentData = data;
}
$scope.recentData = data;
}
function link(scope, element, attr) {
function drawChart (data) {}
scope.$watch('data', function (newD, old) {
drawChart(newD);
}, true);
}
return {link: link, controller: ['$scope', ChartController], restrict: 'EA'}
}
Thats a simplistic example, of what i want to do. The service and data changes work well, i can log the current values. However drawChart() gets called only on startup and not on every mutation.
The Controller has to be part of the directive and not wrapped around
You need to notify angular of the changes by using a method that calls apply() after the change occurs, which is preferably done by using $timeout.
angular.directive("myChart", ['service', '$timeout', function (service, $timeout) {
function ChartController ($scope) {
var ws = new WebSocket("url/to/websocket/");
var data = service.initData(); // initialize data table
ws.onmessage = function (event) { // listen and update data
data = service.updateData(event.data);
$scope.recentData = data;
}
$scope.recentData = data;
}
function link(scope, element, attr) {
function drawChart (data) {}
scope.$watch('data', function (newD, old) {
$timeout(function(){
drawChart(newD);
});
}, true);
}
return {link: link, controller: ['$scope', ChartController], restrict: 'EA'}
}

view not gathering data from service through controller

Having trouble loading an external json file and having it's contents display on my view. I've included my view, controller and services code. What do I need to change?
view.html
<div ng-controller='BaseCtrl'>
<table class="table table-hover">
<tbody>
<tr class="tr-sep" ng-repeat="example in examples" ng-click="showUser(example)">
<td>{{example.name}}</td>
<td>{{example.type}}</td>
<td>{{example.size}}</td>
</tr>
</tbody>
</table>
</div>
controller.js
'use strict';
angular.module('projyApp')
.controller('BaseCtrl', function ($scope, data) {
$scope.examples = data.getAllExamples();
$scope.showUser = function(example) {
window.location = '#/user/' +example.size;
};
});
service.js
'use strict';
angular.module('projyApp')
.service('data', function data() {
var examples;
var getAllExamples = function () {
$http.get("../../TestData/Examples.json").success($scope.examples = data.examples);
};
});
Your service code isn't correct. I see the following problems:
You're creating a local variable getAllExamples that's not accessible from outside the service;
You're using the $http service, but that dependency isn't expressed in the service constructor;
You're trying to update the scope from the service, but it's inaccessible from there. Plus, the $scope variable is not even defined inside the service code.
Here's how your service could look like:
.service('data', function($http) {
this.getAllExamples = function(callback) {
$http.get("../../TestData/Examples.json")
.success(function(data) {
if (callback) callback(data.examples);
});
};
});
And your controller code would be like this:
.controller('BaseCtrl', function ($scope, data) {
data.getAllExamples(function(examples) {
$scope.examples = examples;
});
$scope.showUser = function(example) {
window.location = '#/user/' +example.size;
};
});
You could ditch the callback in the getAllExamples function and work directly with the $http.getreturned promise, but that's a bit more complicated.
Update Added a Plunker script to illustrate the code above.
Main module definition should look like:
angular.module("projyApp",[/*dependencies go here*/]);
Service should look like
//this use of module function retrieves the module
//Note from comments in angular doc: This documentation should warn that "angular.module('myModule', [])" always creates a new module, but "angular.module('myModule')" always retrieves an existing reference.)
angular.module('projyApp')
.service('dataService', [/*dependencies,*/function() {
var service = {
examples:[],
getAllExamples = function () {
$http.get("../../TestData/Examples.json").success(function(returnedData){examples = returnedData});
}
}
return service;
});
Controller should look like:
angular.module('projyApp')
.controller('BaseCtrl', function ($scope, dataService) {
$scope.examples = [];
$scope.showUser = function(example) {
window.location = '#/user/' +example.size;
};
$scope.$watch(function(){return dataService.examples}, function(newVal,oldVal) {$scope.examples = newVal});
});
Also you can add
debugger;
on an line to trigger Chrome to break (like a breakpoint but without having to dig through the scripts at run-time) so long as the Debugging Panel is open (F12)
You should use a callback instead of assigning in to a scope in you data service. By doing that, you can use this function in multiple controllers an assign values to appropriate scopes.
Data Service
var getAllExamples = function (callback) {
$http.get("../../TestData/Examples.json").success(function(data) {
if (typeof callback === "function") callback(data);
});
};
Controller
data.getAllExemples(function(data) {
$scope.examples = data;
});
EDIT
Another what is to create a promise object.
Data Service
var getAllExamples = function () {
return $http.get("../../TestData/Examples.json");
};
Controller
var promise = data.getAllExemples();
promise.then(function(data) {
$scope.examples = data;
});
EDIT 2
In your service, you need to return your functions
angular.module('projyApp')
.service('data', function data() {
var examples;
return {
getAllExamples: function () {
$http.get("../../TestData/Examples.json").success(...);
}
};
});

angularjs directive call function specified in attribute and pass an argument to it

I want to create a directive that links to an attribute. The attribute specifies the function that should be called on the scope. But I also want to pass an argument to the function that is determined inside the link function.
<div my-method='theMethodToBeCalled'></div>
In the link function I bind to a jQuery event, which passes an argument I need to pass to the function:
app.directive("myMethod",function($parse) {
restrict:'A',
link:function(scope,element,attrs) {
var expressionHandler = $parse(attrs.myMethod);
$(element).on('theEvent',function( e, rowid ) {
id = // some function called to determine id based on rowid
scope.$apply(function() {expressionHandler(id);});
}
}
}
app.controller("myController",function($scope) {
$scope.theMethodToBeCalled = function(id) { alert(id); };
}
Without passing the id I can get it working, but as soon as I try to pass an argument, the function is not called anymore
Marko's solution works well.
To contrast with recommended Angular way (as shown by treeface's plunkr) is to use a callback expression which does not require defining the expressionHandler. In marko's example change:
In template
<div my-method="theMethodToBeCalled(myParam)"></div>
In directive link function
$(element).click(function( e, rowid ) {
scope.method({myParam: id});
});
This does have one disadvantage compared to marko's solution - on first load theMethodToBeCalled function will be invoked with myParam === undefined.
A working exampe can be found at #treeface Plunker
Just to add some info to the other answers - using & is a good way if you need an isolated scope.
The main downside of marko's solution is that it forces you to create an isolated scope on an element, but you can only have one of those on an element (otherwise you'll run into an angular error: Multiple directives [directive1, directive2] asking for isolated scope)
This means you :
can't use it on an element hat has an isolated scope itself
can't use two directives with this solution on the same element
Since the original question uses a directive with restrict:'A' both situations might arise quite often in bigger applications, and using an isolated scope here is not a good practice and also unnecessary. In fact rekna had a good intuition in this case, and almost had it working, the only thing he was doing wrong was calling the $parsed function wrong (see what it returns here: https://docs.angularjs.org/api/ng/service/$parse ).
TL;DR; Fixed question code
<div my-method='theMethodToBeCalled(id)'></div>
and the code
app.directive("myMethod",function($parse) {
restrict:'A',
link:function(scope,element,attrs) {
// here you can parse any attribute (so this could as well be,
// myDirectiveCallback or multiple ones if you need them )
var expressionHandler = $parse(attrs.myMethod);
$(element).on('theEvent',function( e, rowid ) {
calculatedId = // some function called to determine id based on rowid
// HERE: call the parsed function correctly (with scope AND params object)
expressionHandler(scope, {id:calculatedId});
}
}
}
app.controller("myController",function($scope) {
$scope.theMethodToBeCalled = function(id) { alert(id); };
}
Not knowing exactly what you want to do... but still here's a possible solution.
Create a scope with a '&'-property in the local scope.
It "provides a way to execute an expression in the context of the parent scope" (see the directive documentation for details).
I also noticed that you used a shorthand linking function and shoved in object attributes in there. You can't do that. It is more clear (imho) to just return the directive-definition object. See my code below.
Here's a code sample and a fiddle.
<div ng-app="myApp">
<div ng-controller="myController">
<div my-method='theMethodToBeCalled'>Click me</div>
</div>
</div>
<script>
var app = angular.module('myApp',[]);
app.directive("myMethod",function($parse) {
var directiveDefinitionObject = {
restrict: 'A',
scope: { method:'&myMethod' },
link: function(scope,element,attrs) {
var expressionHandler = scope.method();
var id = "123";
$(element).click(function( e, rowid ) {
expressionHandler(id);
});
}
};
return directiveDefinitionObject;
});
app.controller("myController",function($scope) {
$scope.theMethodToBeCalled = function(id) {
alert(id);
};
});
</script>
You can create a directive that executes a function call with params by using the attrName: "&" to reference the expression in the outer scope.
We want to replace the ng-click directive with ng-click-x:
<button ng-click-x="add(a,b)">Add</button>
If we had this scope:
$scope.a = 2;
$scope.b = 2;
$scope.add = function (a, b) {
$scope.result = parseFloat(a) + parseFloat(b);
}
We could write our directive like so:
angular.module("ng-click-x", [])
.directive('ngClickX', [function () {
return {
scope: {
// Reference the outer scope
fn: "&ngClickX",
},
restrict: "A",
link: function(scope, elem) {
function callFn () {
scope.$apply(scope.fn());
}
elem[0].addEventListener('click', callFn);
}
};
}]);
Here is a live demo:
http://plnkr.co/edit/4QOGLD?p=info
Here's what worked for me.
Html using the directive
<tr orderitemdirective remove="vm.removeOrderItem(orderItem)" order-item="orderitem"></tr>
Html of the directive: orderitem.directive.html
<md-button type="submit" ng-click="remove({orderItem:orderItem})">
(...)
</md-button>
Directive's scope:
scope: {
orderItem: '=',
remove: "&",
My solution:
on polymer raise an event (eg. complete)
define a directive linking the event to control function
Directive
/*global define */
define(['angular', './my-module'], function(angular, directives) {
'use strict';
directives.directive('polimerBinding', ['$compile', function($compile) {
return {
restrict: 'A',
scope: {
method:'&polimerBinding'
},
link : function(scope, element, attrs) {
var el = element[0];
var expressionHandler = scope.method();
var siemEvent = attrs['polimerEvent'];
if (!siemEvent) {
siemEvent = 'complete';
}
el.addEventListener(siemEvent, function (e, options) {
expressionHandler(e.detail);
})
}
};
}]);
});
Polymer component
<dom-module id="search">
<template>
<h3>Search</h3>
<div class="input-group">
<textarea placeholder="search by expression (eg. temperature>100)"
rows="10" cols="100" value="{{text::input}}"></textarea>
<p>
<button id="button" class="btn input-group__addon">Search</button>
</p>
</div>
</template>
<script>
Polymer({
is: 'search',
properties: {
text: {
type: String,
notify: true
},
},
regularSearch: function(e) {
console.log(this.range);
this.fire('complete', {'text': this.text});
},
listeners: {
'button.click': 'regularSearch',
}
});
</script>
</dom-module>
Page
<search id="search" polimer-binding="searchData"
siem-event="complete" range="{{range}}"></siem-search>
searchData is the control function
$scope.searchData = function(searchObject) {
alert('searchData '+ searchObject.text + ' ' + searchObject.range);
}
This should work.
<div my-method='theMethodToBeCalled'></div>
app.directive("myMethod",function($parse) {
restrict:'A',
scope: {theMethodToBeCalled: "="}
link:function(scope,element,attrs) {
$(element).on('theEvent',function( e, rowid ) {
id = // some function called to determine id based on rowid
scope.theMethodToBeCalled(id);
}
}
}
app.controller("myController",function($scope) {
$scope.theMethodToBeCalled = function(id) { alert(id); };
}