OptimalBPM / angular-schema-form-dynamic-select

AngularStrap and AngularUI dynamic select implementation(static/sync/async/http, filters) for Angular Schema Form
MIT License
55 stars 45 forks source link

Passing scope variable to http post #5

Closed ravivit9 closed 9 years ago

ravivit9 commented 9 years ago

Hello,

Now I am able to get the drop down values from mysql db via php using url http post method. Only issue is how to pass dynamic parameter i.e. passing a scope variable's value to the middleware program in this case to php.

I have hard coded the user id within the form declaration using "parameter" but I would like to use a scope variable (without $scope in the below syntax).

For example: "parameter": { "id" : "CURRENT_USER_ID"} // Where CURRENT_USER_ID is a scope variable.

{
  "key": "org_id",
  "type": "strapselectdynamic",
  "options": {
    "http_post": {
      "url" : "app/server/profiledata/my_organisations.php",
      "parameter": { "id" : "416d0817-43fb-11e4-9e1f-f04da275143b"}
    }
  },
  "placeholder": "Select Organisation Name",
  "htmlClass":"col-lg-4 col-md-4"
}
nicklasb commented 9 years ago

In my view, there are three ways if you want to sent something from the server:

  1. Use a substitution string or set that value manually.
  2. Have the server use session information to set the id property.
  3. Do not do it this way. The server should already know who the current user is through the session cookies.

I would choose the third way, as something as "my_organizations.php" only should be called by the current user.

If you would want to list the organizations for a specified user, then you should call it "user_organizations.php", and then you'd probably set the id as some consequence of a UI action, and then that would be natural to use alternative 1.

nicklasb commented 9 years ago

Well, there is a forth, and quite ugly way, and that is to generate literal Javascript notation on the server and eval() it on the client. I would not recomment that, though.

ravivit9 commented 9 years ago

Thanks for the insights.

Use Case 1: As a logged in user I should be able to list my own organisations in the Edit Profile screen. Note: For this I may be able to use Option 2, but will have to try it.

Use Case 2: As a logged in user I should be able to view other user's profile summary page where all the profile details displayed. One of the section would be displaying list of organisations either in a table format or icons only.
Note: In this case I will have to pass the target user id as a "parameter" to the backend to get all the target user's organisations. If the user clicks MyProfile menu would action the same php script but this time UI will be passing current user id instead of target user id, this is one of the reason why I am not using SESSION based user id.

Renaming .php script should be fine but end of the day the .php file need to know to which user it needs to pull the list of organisation either for the current user or target user. So the .php file shouldn't be knowing which user is being passed but simply accepts any user id passed to it.

nicklasb commented 9 years ago

Ok. Then I would rename my_organizations.php to user_organizations.php If you are listing other users' profiles, and that list comes from the server, it must know their user ids. Hence the server should be able to populate those form parameters in all cases.

BUT, there are more stuff one might want to provide(language settings and other stuff), so how about these ideas:

nicklasb commented 9 years ago

Perhaps this is better: Instead of a general callback, one could have a "callback-map" property.

Then the appropriate callback could be accessed via its name instead, and a string reference is obviously easy to provide via JSON.

I am also then thinking that "async" and "callback" would use that functionality in one way, and http_get/http_post in another,

When thinking about it, this is far more elegant. Also, one could continue to support absolute references from literal object notation: If "callback" is a string, find function through the map, if it is a direct reference to a function, use that function.

ravivit9 commented 9 years ago

Do we have a "callback-map" property? If yes do we have an example of it. Or if allowed to reference scope level factory function by name which returns the JSON value/text array, then that is the most convenient way.

nicklasb commented 9 years ago

I was proposing that a callback map be added.

By name: Well that is also a way to do it. The only problem would be to that I don't think the add-on(or schema-form) knows what the user scope is, it works in its own scope, it would have to have a property added for that, but then again that would apply to the callback map as well. Also, I am not sure that all callbacks will always reside in the same scope. But that would be quite likely, I guess. And if one would need an other scope be called, it could be done using a proxy function.

I will check if there is some way the dynamic-select controller(StrapSelectController) can find the schema-form scope.

ravivit9 commented 9 years ago

Thanks for considering.

For now I will find some workaround how I handle the user id manipulation. But it would be nice to have scope level function name reference on form item level.

nicklasb commented 9 years ago

Well, I have realized I will face the same issue in a couple of weeks, so its kind of something I need to solve anyway.

Yes, the only problem I see it that it that it might have to go through angular-schema-form. And that it could be considered wrong in some way I don't know to access the user scope like that.

nicklasb commented 9 years ago

Hm. The user scope is the $scope.$parent.$parent.$parent.$parent-scope (!) of the dynamic-select controller. I am:

  1. Not sure that is always the case.
  2. Certainly not sure that one is supposed to traverse parent scopes like that in Angular.
ravivit9 commented 9 years ago

One way (sort of expensive) achieving this is to declare user function at $rootScope.myFunc level within user controller which attaches the myFunc at $rootScope hierarchy, then within schema-form or within schema-form-dynamic-select this function can be accessed and executed. This doesn't bubble up through all the way to root scope as it's going to be on straight access to the $rootScope and you have the function there.

Only thing is it needs to be documented that the function need to be attached to $rootScope instead of $scope. Otherwise not many users would prefer to attach function to root level.

How this sounds?

nicklasb commented 9 years ago

I will take it up with the schema-form people, perhaps there is a more elegant solution, it seems that this shouldn't be the first time stuff like this happens.

ravivit9 commented 9 years ago

If you look "Angularjs Schema Form Kitchen Sink Example". Please note there is an onChange property which invokes scope level log() function I believe this is user scope level function called "log".

        "items": [
          {
            "key": "name",
            "placeholder": "Check the console",
            "onChange": "log(modelValue)",
            "feedback": "{'glyphicon': true, 'glyphicon-ok': hasSuccess(), 'glyphicon-star': !hasSuccess() }"
          }

]

nicklasb commented 9 years ago

Hm. I saw that there is an evalInParentScope function. That is probably how they do it. Anyway, if they have it in their example, I suppose that it must be OK to use the same functionality in dynamic select.

I will try doing it that way instead.

nicklasb commented 9 years ago

Good catch, by the way!

nicklasb commented 9 years ago

Great, that seems to work. This is evaluated in the user scope:

$scope.$parent.evalExpr("testfunc()") 

So, how about doing it like this? :

  1. I will use existing options.callback/options.async as they are, but add http_get.optionsCallback/http_post.optionsCallback.
  2. If they are set to a string, I will eval them in the user scope(otherwise i just call the function as I am now).
  3. If that evaluation returns a function reference, I will call that with the options as parameter.
  4. If the call returns to: 4.1 callback/async: I will treat the the return value that as a list of drop down items 4.2. http_post.optionCallback/ http_get.optionsCallback: I will use the return value as the new options
nicklasb commented 9 years ago

Changed the above to get options back instead, no reason to not be able to change all options in the callback.

ravivit9 commented 9 years ago

Great step ahead.

Sounds ok to an extent. Since I used dynamic select not to the fullest, if you could knock down an example covering all of the above aspects then I will be able to fully understand what is your intention.

nicklasb commented 9 years ago

I will implement it in the develop branch tomorrow, an add examples while I am doing that. On 14 Apr 2015 15:24, "ravivit9" notifications@github.com wrote:

Great step ahead.

Sounds ok to an extent. Since I used dynamic select not to the fullest, if you could knock down an example covering all of the above aspects then I will be able to fully understand what is your intention.

Reply to this email directly or view it on GitHub https://github.com/OptimalBPM/angular-schema-form-dynamic-select/issues/5#issuecomment-92830616 .

ravivit9 commented 9 years ago

G8 :-))

Let me know once done, I will install develop version so that I can continue on my project.

nicklasb commented 9 years ago

I just released 0.5.0, it has this functionality, i have tried to improve both the documentation an added examples: https://github.com/OptimalBPM/angular-schema-form-dynamic-select/releases/tag/0.5.0

ravivit9 commented 9 years ago

Thanks for implementing the change.

Today I upgraded the library and performed the change to one of the drop down list. Looks like throwing the following run time error. But function call returns required data from user scope level function.

TypeError: Cannot read property 'then' of undefined at Scope.$scope.fetchResult (http://localhost/bower_components/angular-schema-form-dynamic-select/angular-schema-form-dynamic-select.js:160:70) at $parseFunctionCall (http://localhost/bower_components/angular/angular.js:12332:18) at Scope.$get.Scope.$eval (http://localhost/bower_components/angular/angular.js:14383:28) at ngDirective.compile.pre (http://localhost/bower_components/angular/angular.js:23851:15) at invokeLinkFn (http://localhost/bower_components/angular/angular.js:8213:9) at nodeLinkFn (http://localhost/bower_components/angular/angular.js:7701:11) at compositeLinkFn (http://localhost/bower_components/angular/angular.js:7075:13) at nodeLinkFn (http://localhost/bower_components/angular/angular.js:7717:24) at compositeLinkFn (http://localhost/bower_components/angular/angular.js:7075:13) at publicLinkFn (http://localhost/bower_components/angular/angular.js:6954:30)

JSON

{
  "key": "org_sector_id",
  "type": "strapselectdynamic",
  "options": {
    "asyncCallback": "getSectors"
    },
  "placeholder": "Select Industry / Sector / Domain",
  "htmlClass":"col-lg-4"
}

USING HTTP_POST:

Cannot read property 'httpPost' of undefined at Scope.$scope.fetchResult (http://localhost/bower_components/angular-schema-form-dynamic-select/angular-schema-form-dynamic-select.js:173:43)

{
  "key": "org_sector_id",
  "type": "strapselectdynamic",
  "options": {
    "httpPost": {
      "optionsCallback" : "getSectors",
      "parameter": { "myparam" : "Hello"}
    }
  },

Can you please have a look.

CONTROLLER:

    $scope.getSectors = function () {
        var params = {"id": $scope.userId};
        DataFactory.getData("app/server/lookup/sectors.php", params).then( function(response) {});
    };
nicklasb commented 9 years ago

Hi, The asyncCallback must return a httpPromise. See app.js's callBackMSDAsync for an example. Given that DataFactory.getData seem to return such a promise, something like this should work:

$scope.getSectors = function (options) {
    var params = {"id": $scope.userId};
    return DataFactory.getData("app/server/lookup/sectors.php", params);
};

(added the options parameter for correctness, you could for example store the "app/server/lookup/sectors.php" string in there instead)

nicklasb commented 9 years ago

One question, why do you have the DataFactory? Would these two functionalities make you not have to use a callback?

  1. The ability to reference scope methods and scope variables in forms.
  2. Memoization support
nicklasb commented 9 years ago

BTW, I am currently implementing filters. Not sure if you need those, but FYI, they are in the develop branch.

ravivit9 commented 9 years ago

The callBackMSDAsync example uses $scope.callBackMSDAsync which I cannot reference like that within JSON

nicklasb commented 9 years ago

That is just an example of one way to use it, all callbacks can be either by name or reference now. It could just as well be "callBackMSDAsync".

See the "Feature summary"-section for a description for how all variants can be combined.

ravivit9 commented 9 years ago

I tried both ways of calling a scope function but returns the same error as ERROR: TypeError: Cannot read property 'then' of undefined at Scope.$scope.fetchResult (http://localhost/bower_components/angular-schema-form-dynamic-select/angular-schema-form-dynamic-select.js:160:70)

METHOD 1 : "httpPost": { "optionsCallback" : "getSectors", "parameter": { "id" : "sample"} }

METHOD 2 : { "key": "org_sector_id", "type": "strapselectdynamic", "options": { "asyncCallback": "getSectors" }, "placeholder": "Select Industry / Sector / Domain", "htmlClass":"col-lg-4" },

Just as a reference self.getData = function(phpUrl, params) { //returns a promise which when fulfilled returns the name. this.DataFactory = function() { self.selfData=null; var deferred = $q.defer(); $http.post(phpUrl, params) .success(function(response) { self.selfData = response; deferred.resolve(response); }) .error(function(response) { deferred.reject(response); }); return deferred.promise; }; }

From the above factory function do you thing it's returning a promise?

If you think the above is not returning a promise then could you please mock a sample factory function which returns a promise, so that I can make necessary changes to my program.

I am not sure whether I am missing something here.

nicklasb commented 9 years ago

Well, self.getData doesn't return anything as this.DataFactory isn't called and thus, its value never returned?

(BTW, a leading line and a four-space indent created a more readable code-block like the one below)

self.getData = function (phpUrl, params) { //returns a promise which when fulfilled returns the name.
    this.DataFactory = function () {
        self.selfData = null;
        var deferred = $q.defer();
        $http.post(phpUrl, params)
            .success(function (response) {
                self.selfData = response;
                deferred.resolve(response);
            })
            .error(function (response) {
                deferred.reject(response);
            });
        return deferred.promise;
    };
}
ravivit9 commented 9 years ago

The factory function works fine, i just sent the part of the code. The below is the full factory code.

With regards to formatting even though while pasting the formatting is there but after saving it's loosing it's format.

angular.module('myApp')
   .factory('DataFactory', function($http, $q, AppFactory) {
      this.DataFactory = function() {
                //THE self.getData function here as ABOVE CODE
       };
        return new this.DataFactory();
    });

By doing this way I can have multiple factory functions within the DataFactory.

Please find the below screenshot where console.log prints the data I believe this log is being printed within dynamic select as I am not printing anywhere.

sectors

nicklasb commented 9 years ago

Well, I just saw the code you sent me. I presumed that something was missing, but I couldn't base my response on assumption. Also, I you want me to debug your code for you, I'll advice you to 1) try and make it as easy as possible for me, and 2) link to directly to the repository. Because a crucial part of the code, the "getSectors"-implementation is also missing? I suppose that should just pass on the promise to dynamic select?

As you want to use a callback for the items http_post is not for you unless you just set the options.url

WRT formatting I just edited your text and added four spaces to each row, it seem to work?

ravivit9 commented 9 years ago

From your point of view I understand how difficult to understand when I specify bits of code.

The getSectors scope function is in controller.

$scope.getSectors = function () { var params = {"id": $scope.userId}; DataFactory.getData("app/server/lookup/sectors.php", params).then( function(response) {}); };

From reading through the above comments it's just missing "return" keyword just before DataFactory.getData...... if "return" is necessary then I will have to update my controller function to have it mentioned.

Possibly the DataFactory function is returning the promise to the user scope function getSectors, since I haven't mentioned "return" getSectors function it doesn't return anything to the caller.

nicklasb commented 9 years ago

Yes, the function must return a promise to dynamic select.

ravivit9 commented 9 years ago

I understand the function must return a promise. From your point of you can you say the above DataFactory.getData function returns promise?

nicklasb commented 9 years ago

getData seems to do so, yes.

But I would write a test for it to make sure if you are uncertain.

ravivit9 commented 9 years ago

No Luck, it doesn't seems to be accepting what I return as promise to the dynamic select despite dynamic select outputs console.log with the drop down list data which got via the factory call.

I am out of option now. May be I will fall back to httpPost url option and may be do some work around to get the drop down populated.

ravivit9 commented 9 years ago

The example in app.js is scope functions, if you could extend your example with a demo factory function and use that function to return promise and pass it to the dynamic select. As everthing works fine but it's a bit grey area why dynamic select partially accepting what was return from the getSectors scope function but refuses to populate the dropdown and throwing the above ".then error"

ravivit9 commented 9 years ago

Haaaan. This time worked fine.

Changed the following scope function FROM $scope.getSectors = function () { var params = {"id": $scope.currentUser.id}; return DataFactory.getData("app/server/reference_lookup/sectors.php", params).then( function(response) { }); //DataFactory.returnDataPromise("app/server/reference_lookup/sectors.php", };

TO $scope.getSectors = function () { var params = {"id": $scope.currentUser.id}; return DataFactory.getData("app/server/reference_lookup/sectors.php", params); };

I just removed .then from the scope function then it worked fine.

Finally managed to make it work locally.

Thanks.

nicklasb commented 9 years ago

Aha, I see, I didn't see that you called .then() in getSectors().

Well, the http-promise object is a promise object that not only has .then(), but http-specific .success() and .error() functions.

Promises are actually kind of confusing at first, it takes some time to really "get it".

For example, as far as I know, you should be able to call .then() right there, as i seems to return the promise, too. There is no reason to, of course, and nobody does it, but looking at the source it should work.

It could have something to do with that the function(response){} you provide is empty. But I am not sure.

Actually, the http-promise is usually configured using $http.post().success().error() which implies that that .success() returns the the promise, or the .error() call simply wouldn't work.

nicklasb commented 9 years ago

@ravivit9 ; I have just started this thing, it is supposed to provide a generalized way of spreading information around a client. For example if an Organization has been added somewhere so that a dynamic-select can learn about it and know that it should reload itself...

nicklasb commented 9 years ago

@ravivit9 : Just wanted to mention to you that there is a new version that is more like ASF and uses titleMaps instead of items.

https://github.com/OptimalBPM/angular-schema-form-dynamic-select/releases/tag/v0.8.0

ravivit9 commented 9 years ago

Hi, Sure will have a look.  Thanks for letting me know. Regards Ravindra. On 10 May 2015 11:48 pm, Nicklas Börjesson notifications@github.com wrote:@ravivit9 : Just wanted to mention to you that there is a new version that is more like ASF and uses titleMaps instead of items.

https://github.com/OptimalBPM/angular-schema-form-dynamic-select/releases/tag/v0.8.0

—Reply to this email directly or view it on GitHub.

nicklasb commented 9 years ago

Closing this now.