How to properly bind scope between directive and controller with angularJS - scope

I'm trying to generate an n-level hierarchical unordered list with anugularJS, and have been able to successfully do so. But now, I'm having scope issues between the directive and controller. I need to change a scope property of the parent from within a function called via ng-click in the directive template.
See http://jsfiddle.net/ahonaker/ADukg/2046/ - here's the JS
var app = angular.module('myApp', []);
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
function MyCtrl($scope) {
$scope.itemselected = "None";
$scope.organizations = {
"_id": "SEC Power Generation",
"Entity": "OPUNITS",
"EntityIDAttribute": "OPUNIT_SEQ_ID",
"EntityID": 2,
"descendants": ["Eastern Conf Business Unit", "Western Conf Business Unit", "Atlanta", "Sewanee"],
children: [{
"_id": "Eastern Conf Business Unit",
"Entity": "",
"EntityIDAttribute": "",
"EntityID": null,
"parent": "SEC Power Generation",
"descendants": ["Lexington", "Columbia", "Knoxville", "Nashville"],
children: [{
"_id": "Lexington",
"Entity": "OPUNITS",
"EntityIDAttribute": "OPUNIT_SEQ_ID",
"EntityID": 10,
"parent": "Eastern Conf Business Unit"
}, {
"_id": "Columbia",
"Entity": "OPUNITS",
"EntityIDAttribute": "OPUNIT_SEQ_ID",
"EntityID": 12,
"parent": "Eastern Conf Business Unit"
}, {
"_id": "Knoxville",
"Entity": "OPUNITS",
"EntityIDAttribute": "OPUNIT_SEQ_ID",
"EntityID": 14,
"parent": "Eastern Conf Business Unit"
}, {
"_id": "Nashville",
"Entity": "OPUNITS",
"EntityIDAttribute": "OPUNIT_SEQ_ID",
"EntityID": 4,
"parent": "Eastern Conf Business Unit"
}]
}]
};
$scope.itemSelect = function (ID) {
$scope.itemselected = ID;
}
}
app.directive('navtree', function () {
return {
template: '<ul><navtree-node ng-repeat="item in items" item="item" itemselected="itemselected"></navtree-node></ul>',
restrict: 'E',
replace: true,
scope: {
items: '='
}
};
});
app.directive('navtreeNode', function ($compile) {
return {
restrict: 'E',
template: '<li><a ng-click="itemSelect(item._id)">{{item._id}} - {{itemselected}}</a></li>',
scope: {
item: "=",
itemselected: '='
},
controller: 'MyCtrl',
link: function (scope, elm, attrs) {
if ((angular.isDefined(scope.item.children)) && (scope.item.children.length > 0)) {
var children = $compile('<navtree items="item.children"></navtree>')(scope);
elm.append(children);
}
}
};
});
and here's the HTML
<div ng-controller="MyCtrl">
Selected: {{itemselected}}
<navtree items="organizations.children"></navtree>
</div>
Note the list is generated from the model. And ng-click calls the function to set the parent scope property (itemselected), but the change only occurs locally. Expected behavior, when I click on an item, is that "Selected: None" should change to "Selected: xxx" where xxx is the item that was clicked.
Am I not binding the property between the parent scope and the directive appropriately? How do I pass the property change to the parent scope?
Hope this is clear.
Thanks in advance for any help.

Please have a look at this working fiddle, http://jsfiddle.net/eeuSv/
What i did was to require the parent controller inside the navtree-node directive, and call a member function defined in that controller.
The member function is setSelected. Please note that its this.setSelected and not $scope.setSelected.
Then define a navtree-node scope method itemSelect. While you click on the anchor tags, it will call the itemSelect method on the navtree-node scope. This inturn will call the controllers member method setSelected passing the selected id.
scope.itemSelect = function(id){
myGreatParentControler.setSelected(id)
}

Maxdec is right, this has to do with scoping. Unfortunately, this is a case that's complicated enough that the AngularJS docs can be mis-leading for a beginner (like myself).
Warning: brace yourself for me being a little long-winded as I attempt to explain this. If you just want to see the code, go to this JSFiddle. I've also found the egghead.io videos invaluable in learning about AngularJS.
Here's my understanding of the problem: you have a hierarchy of directives (navtree, navitem) and you want to pass information from the navitem "up the tree" to the root controller. AngularJS, like well-written Javascript in general, is set up to be strict about the scope of your variables, so that you don't accidentally mess up other scripts also running on the page.
There's a special syntax (&) in Angular that lets you both create an isolate scope and call a function on the parent scope:
// in your directive
scope: {
parentFunc: '&'
}
So far so good. Things get tricky when you have multiple levels of directives, because you essentially want to do the following:
Have a function in the root controller that accepts a variable and update the model
A mid-level directive
A child-level directive that can communicate with the root controller
The problem is, the child-level directive cannot see the root controller. My understanding is that you have to set up a "chain" in your directive structure that acts as follows:
First: Have a function in your root controller that returns a function (which has reference to the root view controller's scope):
$scope.selectFunctionRoot = function () {
return function (ID) {
$scope.itemselected = ID;
}
}
Second: Set up the mid-level directive to have it's own select function (which it will pass to the child) that returns something like the following. Notice how we have to save off the scope of the mid-level directive, because when this code is actually executed, it will be in the context of the child-level directive:
// in the link function of the mid-level directive. the 'navtreelist'
scope.selectFunctionMid = function () {
// if we don't capture our mid-level scope, then when we call the function in the navtreeNode it won't be able to find the mid-level-scope's functions
_scope = scope;
return function (item_id) {
console.log('mid');
console.log(item_id);
// this will be the "root" select function
parentSelectFunction = _scope.selectFunction();
parentSelectFunction(item_id);
};
};
Third: In the child-level directive (navtreeNode) bind a function to ng-click that calls a local function, which will, in turn, "call up the chain" all the way to the root controller:
// in 'navtreeNode' link function
scope.childSelect = function (item_id) {
console.log('child');
console.log(item_id);
// this will be the "mid" select function
parentSelectFunction = scope.selectFunction();
parentSelectFunction(item_id);
};
Here's the updated fork of your JSFiddle, which has comments in the code.

It may be because each directive creates his own scope (actually you tell them to do so).
You can read more about directives here, especially the chapter "Writing directives (long version)".
scope - If set to:
true - then a new scope will be created for this directive. If
multiple directives on the same element request a new scope, only one
new scope is created. The new scope rule does not apply for the root
of the template since the root of the template always gets a new
scope.
{} (object hash) - then a new 'isolate' scope is created. The
'isolate' scope differs from normal scope in that it does not
prototypically inherit from the parent scope. This is useful when
creating reusable components, which should not accidentally read or
modify data in the parent scope.
So the changes you do are not reflected in the MyCtrl scope because each directive has his own 'isolated' scope.
That's why a click only changes the local $scope.itemselected variable and not 'all' of them.

Related

Return type of transaction?

I'm running chaincode-java from fabric-samples.
#Transaction(intent = Transaction.TYPE.EVALUATE)
public ArrayList<Asset> GetAllAssets(final Context ctx) {
ChaincodeStub stub = ctx.getStub();
ArrayList<Asset> queryResults = new ArrayList<Asset>();
// To retrieve all assets from the ledger use getStateByRange with empty startKey & endKey.
// Giving empty startKey & endKey is interpreted as all the keys from beginning to end.
// As another example, if you use startKey = 'asset0', endKey = 'asset9' ,
// then getStateByRange will retrieve asset with keys between asset0 (inclusive) and asset9 (exclusive) in lexical order.
QueryResultsIterator<KeyValue> results = stub.getStateByRange("", "");
for (KeyValue result: results) {
Asset asset = genson.deserialize(result.getStringValue(), Asset.class);
System.out.println(asset);
queryResults.add(asset);
}
// final String response = genson.serialize(queryResults);
return queryResults;
}
The GetAllAssets() method was returning String, but I changed it to ArrayList.
As a result, GetAllAssets throws error when invoked.
$ peer chaincode query -C mychannel -n basic -c '{"Args":["GetAllAssets"]}'
Error: endorsement failure during query. response: status:500 message:"Unexpected error"
The log says
Thread[fabric-txinvoke:2,5,main] 11:15:01:224 INFO org.hyperledger.fabric.contract.ContractRouter processRequest Got invoke routing request
Thread[fabric-txinvoke:2,5,main] 11:15:01:226 INFO org.hyperledger.fabric.contract.ContractRouter processRequest Got the invoke request for:GetAllAssets []
Thread[fabric-txinvoke:2,5,main] 11:15:01:234 INFO org.hyperledger.fabric.contract.ContractRouter processRequest Got routing:GetAllAssets:org.hyperledger.fabric.samples.assettransfer.AssetTransfer
Thread[fabric-txinvoke:2,5,main] 11:15:01:274 SEVERE org.hyperledger.fabric.Logger error nulljava.lang.NullPointerException
at org.hyperledger.fabric.contract.execution.JSONTransactionSerializer.toBuffer(JSONTransactionSerializer.java:84)
at org.hyperledger.fabric.contract.execution.impl.ContractExecutionService.convertReturn(ContractExecutionService.java:89)
at org.hyperledger.fabric.contract.execution.impl.ContractExecutionService.executeRequest(ContractExecutionService.java:67)
at org.hyperledger.fabric.contract.ContractRouter.processRequest(ContractRouter.java:123)
at org.hyperledger.fabric.contract.ContractRouter.invoke(ContractRouter.java:134)
at org.hyperledger.fabric.shim.impl.ChaincodeInvocationTask.call(ChaincodeInvocationTask.java:106)
at org.hyperledger.fabric.shim.impl.InvocationTaskManager.lambda$newTask$17(InvocationTaskManager.java:265)
at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1736)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Thread[fabric-txinvoke:2,5,main] 11:15:01:276 SEVERE org.hyperledger.fabric.shim.impl.ChaincodeInvocationTask call [13733a23] Invoke failed with error code 500. Sending ERROR
Can I return List from a transaction? Besides String, what other types can I return? Is there any documentation that I can take a look?
Bit of background first; the ContractAPI that is available in Java, Go and Typescript is used to generate a 'model' of the overall contract including the data type that be passed and returned from transaction functions. (JavaScript supports a limited subset to the extent possible based on it's typing).
In order to support this there has to be a 'serializer' of some sort to process the data. The underlying chaincode API of just 'invoke(byte[]): byte[]' gives the developer the power to serialize how they wish though not all of us need to use that power.
There is a default 'serializer' in the ContractAPI; this can be swapped out if needed.
To specifically answer the question;
The return types can be:
strings,
numbers (for Java this is any of the primitive 'number' types)
booleans,
other types that have been annotated.
arrays of the above
For the 'other types', there are annotations that can be used to define types that can also be passed to and from the transaction functions.
You might see something like this:
#DataType()
public final class Complex {
#Property()
private final String id;
#Property()
private final Description description;
#Property()
private final int value;
public String getID() {
return id;
}
public int getValue() {
return value;
}
public Description getDescription(){
return description;
}
}
Description there is also a class annotated in a similar manner.
This would produce the Contract Metadata that would look like
"Description": {
"$id": "Description",
"type": "object",
"properties": {
"colour": {
"type": "string"
},
"owners": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"Complex": {
"$id": "Complex",
"type": "object",
"properties": {
"id": {
"type": "string"
},
"value": {
"type": "number"
},
"description": {
"$ref": "Description"
}
}
}
On the Contract Model, or Contract Metadata
There is a JSON schema for this at
https://github.com/hyperledger/fabric-chaincode-node/blob/main/apis/fabric-contract-api/schema/contract-schema.json
Isn't this restrictive? what about Lists?
It's a fair comment, from a Java perspective, something like an ArrayList or Map would be a reasonable thing to return. However the challenge is that it is possible for the contracts to be implemented in different languages. Plus once deployed the Contract will be running for some time, therefore the metadata provides a strong 'API Definition' between the Smart Contract and the Client Application.
What transaction functions (also in the metadata) will be clearly defined.
Summary
I would like to provide some more examples (and docs!) but wanted to get this written up first. There are extensions and changes we could make, and would like to make but there are only so many hours!
As a maintainer of these repos, we'd love to have people come on board if this is an area of interest.

Error when using a precompiled Azure Function with extra methods

I have created a very simple Precompiled function (copied code from tool generated):
public class Foo
{
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req)
{
//log.Info($"C# HTTP trigger function processed a request. RequestUri={req.RequestUri}");
// parse query parameter
string name = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, "name", true) == 0)
.Value;
// Get request body
dynamic data = await req.Content.ReadAsAsync<object>();
// Set name to query string or body data
name = name ?? data?.name;
return name == null
? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a name on the query string or in the request body")
: req.CreateResponse(HttpStatusCode.OK, "Hello " + name);
}
}
The dll this resides in is copied to the Function's folder and is linked up in function.json like this:
{
"scriptFile": "ExternalFunction.dll",
"entryPoint": "ExternalFunction.Foo.Run",
"disabled": false,
"bindings": [
{
"authLevel": "function",
"name": "req",
"type": "httpTrigger",
"direction": "in"
},
{
"name": "res",
"type": "http",
"direction": "out"
}
]
}
This all works fine.
What I then wanted to do was add a private method to be called from the Run method, so (baby steps) I added this to the Foo class:
private static string Test()
{
return "Hello";
}
This results in these errors in the CLI tools:
error AF007: A method matching the entry point name provided in
configuration ('ExternalFunction.Foo.Run') does not exist. Your
function must contain a single public method, a public method named
'Run', or a public method matching the name specified in the
'entryPoint' metadata property. Function compilation error error
AF007: A method matching the entry point name provided in
configuration ('ExternalFunction.Foo.Run') does not exist. Your
function must contain a single public method, a public method named
'Run', or a public method matching the name specified in the
'entryPoint' metadata property.
Which is a very odd message as surely adding the private static method should have no effect on Functions being able to find the public method specified in function.json?!
Any ideas?
This is indeed odd.
I'll work on a repro and open an issue to address the problem if this turns out to be a defect (I'll update the issue or the results of my investigation), but in the meantime, you should be able to create those methods in a different class (static or otherwise) and call that method on that class.

Check for 'http://' text in json object with Chai.js Should

I've got some nested objects inside each object and I want to check with chai that the images's hrefs start with 'http://'
{
"images": [
{
"header": {
"href": "http://somedomain.com/assets/header.jpg"
}
},
{
"logo": {
"href": "http://somedomain.com/assets/logo.jpg"
}
}
]
}
The problem is, I can't just key off of the image name property because it changes...so I can't do this:
images[0].[image name changes!! it's not a concrete property name].href.should.have.deep.property("href");
because imagename is like 'header', 'logo', and so on
so how would I be able to do this with chai and check the href for each image to make sure it's got the text 'http://'?
You could dynamically iterate through all objects in the images array as #gfpacheco suggested in his answer.
But I would research a way to create a deterministic test. This would simplify your assertions, but might require some creativity or refactoring to mock or render fixtures
I tried to write the entire Chai assertion function. It uses the Chai Things plugin to work with assertions over arrays:
var data = {...};
function startsWithHttp(string) {
return string.indexOf('http://') === 0;
}
data.images.should.all.satisfy(function(image) {
Object.keys(image).should.contain.an.item.that.satisfy(function(key) {
image[key].should.have.all.keys('href');
image[key].href.should.satisfy(startsWithHttp);
});
});
It assumes that all images should have at least one property that it's value must have an href property and it's value starts with http://.
As I said #dm03514, it's hard to find a deterministic method. I get the sub string of href and would verify if is equals (and iterate how #gfpacheco did do). I tried let the same environment for their:
var http = "http://";
if(header.href.substr(0,7) === http)
alert("Header ok");
else
alert("Header no ok")
https://jsfiddle.net/lbclucascosta/zhrfLa8m/2/
Obs: This is pure javascript, but you can get the substring in node too. nodejs: string manipulation

Statically load a QML Component without instantiating it

As I understand it, a QML Component is like a kind of like a class in C++. It contains the definition of a QML object but isn't an instance of it. You can create a Component in these ways:
Creating a .qml file with the component name as its filename.
Define it inline with the Component { } syntax.
However these are actually two different things. The second one is more like a factory because you can do things like:
Component {
id: factory
Rectangle { width: 100; height:100; color: "red }
}
Component.onCompleted: {
var rect1 = factory.createObject(parent);
}
Whereas with the separate file you need to first load it into a factory like this:
var factory = Qt.createComponent("RedRectangle.qml")
var rect1 = factory.createObject(parent);
I'm only concerned with dynamic object creation, so this is not an option:
RedRectangle {
id: rect1
}
My question is: is there a way to create the objects dynamically, without having to create the Component factory dynamically too, and without having to specify the Component inline. I.e. I want the first example, but where the Rectangle is specified in another file.
I want this:
Component {
id: factory
url: "RedRectangle.qml"
}
Component.onCompleted: {
var rect1 = factory.createObject(parent);
}
Sadly that doesn't work. I also tried this:
Component {
id: factory
}
Component.onCompleted: factory.loadUrl("RedRectangle.qml");
But it doesn't work either. Am I being stupid or is this just not supported?
Here is some encapsulation:
Fact.qml (for some reason it doesn't let me name it Factory)
QtObject {
property string url
readonly property Component component : Qt.createComponent(url)
function get() { return component }
function load(url) { return Qt.createComponent(url) }
}
usage:
Fact {
id: f
url: "RedRect.qml"
}
StackView {
id: stack
}
Component.onCompleted: {
stack.push(f.component) // redrect
f.url = "BlueRect.qml"
stack.push(f.get()) // bluerect, redundant but shorter
stack.push(f.load("GreenRect.qml")) // greenrect, f.component is still bluerect
}
It will only load the component when its component property is referenced and you can change the url to load other components with the same Fact instance. Also the auxiliary load() method, which returns a component without actually changing the one potentially cached.
Actually the answer is not too bad, though I still think Component should support specifying a url directly.
Here is my solution:
property var factory: Qt.createComponent("RedRectangle.qml")

Can I add attributes to a Backbone View?

I have been working with backbone for a while and I am now using a number of views. In some of my views I sometimes add custom attributes like:
var DataGrid = Backbone.View.extend({
className:"datagrid",
lookup: {
header: "", //Header wrapper row element
headers: [], //Views in header
body: "", //Body wrapper row element
rows: [] //Views in body
},
events: {
...
},
initialize: function() {
...
},
render: function() {
...
}
});
As you can see I have "lookup" as an extra attribute to the Object. I use DataGrid in a number of my views and I am experiencing a very strange behaviour. When I switch between views that use DataGrid, "lookup" would still be populated with the old data. I use "new" when creating a new DataGrid but I still find old data. Am I missing something?
EDIT: Following #rabs reply. I did a search on static variables in Backbone and found this: Simplify using static class properties in Backbone.js with Coffeescript
I know an answer has been accepted on this (a while ago), but as I came across this question while working on a backbone project recently, I thought it would be worth mentioning that you can define attributes as a function also. This is especially useful for views that need to have attributes set to values in their current models.
By defining attributes as a function you can do something like
var myObject = Backbone.View.extends({
attributes: function() {
if(this.model) {
return {
value: this.model.get('age')
}
}
return {}
}
});
Hope that helps someone
Declaring variables in this way the scope of the variable is to the class not the instance, similar to s static or class variable.
So yeah the lookup object will shared between your different instances.
You could pass the lookup object in to your instance when you create it that way it will behave as an instance variable.

Resources