Peg.js in AngularJS webapp - angularjs

I have an AngularJS web application.
I'd like to use peg.js in my application.
I've just written a peg.js grammar: CriteriaValue.pegjs and generated the parser with the command line:
pegjs CriteriaValue.pegjs, which generated CriteriaValue.js.
Could someone explain to me how to use the parser ?
var result = parser.parse('my string'); doesn't work.
I've created a plunker:
http://plnkr.co/edit/Ae05SeZAjKOQ75B3lvLc?p=preview

Short Answer
In CriteriaValue.js, change module.exports in the first line to parser
In index.html, swap the <script> tags so that CriteriaValue.js comes first
(Optional) In script.js, output the results of your parse as a formatted JSON string, in order to see the actual values
Here's the plunker: http://plnkr.co/edit/kiBp2Na9s4PXpenCzQjx?p=preview
Long Answer
Run your original plunker and check the console logs; you'll notice 2 errors:
ReferenceError: Can't find variable: parser (script.js:3)
ReferenceError: Can't find variable: error (CriteriaValue.js:1)
The first error is due to the fact that no parser object is ever created in global scope, by script.js or by CriteriaValue.js.
Looking at CriteriaValue.js, you can see it's actually assigning the generated parser object to a non-existent modules.export. This is the default behavior of PEG.js, as it assumes you're going to use your parser with node.js. The reason you're seeing the error is that there is no module object, so we can't assign to this non-existent object's export property. Changing the assignment to parser, which is something we can assign to (because PEG.js doesn't use strict mode), avoids this error and makes parser available for use in script.js.
Finally, the parser needs to be created before script.js can use it; hence the reason for the <script> swap.
For future creation of CriteriaValue.js, do it like this:
pegjs --export-var parser CriteriaValue.pegjs
This will generate the file so that the object is assigned to the variable parser instead of module.exports.
Where AngularJS Comes In
As #dirkk said in the comments, defining the parser as a global variable is bad practice and certainly not the AngularJS way, which is to implement your parser as a service.
The quickest (but not necessarily best) way to do that is to take your already generated CriteriaValue.js code and wrap a service around it. e.g.:
angular.module('yourApp.services', [])
.factory('Parser', function() {
// The generated code, except replace "parser = " with "return "
});
Another option is to fetch the .pegjs file & generate your parser on the client using PEG.buildParser():
angular.module('yourApp.services', [])
.factory('Parser', ['$http', '$q', function($http, $q) {
var deferred = $q.defer();
$http.get('path/to/CriteriaValue.pegjs')
.success(function(grammar) {
try {
deferred.resolve(PEG.buildParser(grammar));
} catch (e) {
deferred.reject(e);
}
})
.error(function(message) {
deferred.reject('Unable to load grammar: ' + message);
});
return deferred.promise;
}]);
This makes updating your grammar easier, as you won't have to rewrite your service every time, but it adds a delay in loading your app. The feasibility of this depends on how complex your grammar is & how often it actually needs to change.
Despite how you build your parser, you don't necessarily need to expose the generated parser object directly to the rest of your Angular app. Instead, you can implement a higher-level API for what your app will actually do with this parser (e.g. validate(input), getAST(input), etc...). This way, if you decide in the future to switch to a different parser (e.g. Jison), you'll have much less code to change.

Related

Getting hold of loaded javascript objects in Protractor

In our application we load requirejs, which in return loads angularjs, and also other javascript modules. I am wondering if there any way to get hole of these LOADED modules (angularjs, javascript modules) in protractor test. Note, we want the instance that is loaded by the browser when running Protractor, we don't want to create instance by ourselves.
Any suggestion or example?
Thanks in advance.
Nick Tomlin's answer is what you can do if a module returns serializable data structure as a value. You call require and call with the module's value the callback that executeAsyncScript gives you to allow returning asynchronous values. This will work, for instance, if your module returns "foo" or { foo: 'bar' } or structures that are generally serializable.
However, it won't always work. Complex modules cannot be retrieved that way. Roughly speaking you should expect what you send through executeScript and executeAsyncScript and what they return to have the same limitations as JSON.stringify does. One major exception is that Selenium will wrap DOM objects returned from these calls into a structure that allows to identify them on the script side, and that allows passing them back to the browser. (Then again, there are limitations there too. This is why you get stale element exceptions, for instance.)
If you try to retrieve modules that export functions, you'll probably get something but it won't be complete. Try this, for instance:
browser.executeAsyncScript(function () {
arguments[0]({ foo: function () {}});
}).then(function (value) {
console.log(value);
});
The output I get is:
Object { foo: Object {} }
The function has been turned into an empty object.
I do not use angular with require.js, but i'm assuming you could access the require'd angular the same way you would in a module:
var pageAngular = browser.driver.executeAsyncScript(function () {
var callback = arguments[arguments.length - 1];
require(['angular'], function (angular) {
callback(angular);
})
});
The use of executeAsync is necessary here, since AMD modules are loaded asynchronously.
Do note that as #louis noted, the return of executeAsyncScript is going to be a serialized object, not the "live" instance of angular. If you need to interact with angular within the context of your page, you should do so within the callback torequire.
Something like this should do it:
var angular = browser.driver.executeScript("return window.angular;");

Call translation service from a callback registered in an app.config section

I'm relatively new to AngularJS and the problem I'm facing is one of those "I want to inject a Service into an app.config" type of scenarios, which I realise cannot be done. (I'm comfortable with the different between Service and Provider, and why a Service cannot be injected into a .config.)
What I am trying to accomplish is to use angular-schema-form together with angular-translate such that field titles in generated forms are translated.
There is an issue where someone asks how to do this, and the advice given is to take advantage of angular-schema-form's postProcess, which is a property of the Provider. This callback gives you the form object before it is rendered, giving you the opportunity to manipulate it with user code. Therefore translation could be done within here.
The postProcess method is called on the Provider, so it is done within an app.config:
app.config(function(schemaFormProvider, $translateProvider) {
schemaFormProvider.postProcess(function(form){
// within here I can inspect the form object, find all
// properties whose key is "title", and then perform
// language translation on their values.
So, that is apparently the place where I have an opportunity to manipulate control titles and so on.
Over to the angular-translate library, for me to 'manually' translate strings, I can use the $translate service. This provides both synchronous and asynchronous methods to translate a given key string. The synchronous one is $translate.instant(key).
To glue these two together, what I have tried so far (which does work) is to create a 'bridge' method like this:
var app = angular.module('myApplicationName', ['schemaForm', 'pascalprecht.translate']);
....
app.config(function(schemaFormProvider, $translateProvider) {
schemaFormProvider.postProcess(function(form){
// ... code here which iterates over properties
// and finds all control titles ...
key = app.myTranslate(key);
// ....
}
....
});
app.myTranslate = function (key) {
var service = angular.injector(['ng', 'myApplicationName']).get("$translate");
return service.instant(key);
}
This does work, but it seems ugly and unsafe (as presumably there's no guarantee $translate is ready when the callback is first invoked) and the calls to angular.injector(['ng', 'myApplicationName']).get... are presumably expensive.
Is there a better way, or is this the only way I'm going to get it done, considering the constraints of the libraries I'm working with?
I have also considered an alternative approach altogether, which would be to instead perform the translations on the schema or form objects before they are processed by angular-schema-form. This could be done from within Controllers, eliminating the problem of accessing the $translate service. I may end up going down that route, but it would still be nice to understand the best solution for the above scenario.

Reactive non-mongo variable in angular-meteor

I couldn't find an answer or a solution to a challenge yet: How can I bind a variable (Session variable, no mongo collection) reactively in angular-meteor?
I'm converting from standalone meteor. Here I could use the template.helper method. As I can't use templates (and iron:router) anymore with angular-meteor and angularui-router, I can't bind reactivity to the helper anymore (at least in my understanding).
I tried this in an meteor-angular controller, which belongs to a sentence.tpl file:
$scope.parsestring = function(input_string){
tokenizer(input_string);
};
$scope.sentence_type = Session.getJSON("current_sentence.sentence_type");
Tokenizing works (I can see it in the debugger), but the value is only displayed, when I reload the page. What I want to achieve is tokenizing a string from an input field into a JSON representation (the tokenizer takes care of that) and displaying it similtaniously from the JSON representation in a structured way (separate html input elements, which are created dynamically). sentence_type is the variable that should be used on the html-page to show and change the sentence type, which can change while typing.
Anybody has some hints? Maybe, I could also use some Angular feature that I don't know?
Cheers,
Jan
Code repo:
My current code looks like this:
My code looks similar to this:
angular.module('ngaignt').controller("InteractorCtrl", ['$scope', '$meteor', '$meteorCollection',
function ($scope, $meteor, $meteorCollection) {
// Autorun is necessary to make reactive variables out of the JSON returns
var c = Tracker.autorun(function (comp) {
$scope.verb_type = Session.getJSON("current_verb.type");
$scope.object_type = Session.getJSON("current_object.type");
$scope.verb_attributes = _.toArray(Session.getJSON("current_verb.attributes"));
$scope.object_attributes = _.toArray(Session.getJSON("current_object.attributes"));
if (!comp.firstRun) {
// only do not do aply at first run becaulse then apply is already running.
$scope.$apply();
}
});
$scope.parsestring = function (input_string) {
interactor(input_string);
};
//$scope.on('$destroy', function () {c.stop()});
}]);
To use reactive variables, you need a reactive computation. You may need to use Tracker.autorun:
$scope.parsestring = Tracker.autorun(function(someStringInSession){
tokenizer(Session.get(someStringInSession));
});
Or you can use Tracker.autorun(func) wherever you use a reactive variable to reactively rerun a function when the variable changes.
good question and the best answer depend on your needs.
There are 2 possible solutions:
If you want to bind a Session variable to a scope variable, use the $meteorSession service.
What it does is that every time the scope variable will change, it will change to Session variable (and trigger an autorun if it's placed inside one).
and every time the Session variable will change, the scope variable will change as well (and change the view that it's placed upon).
If you are using the Session variable just to get a variable reactive (meaning trigger an autorun), you should use getReactively . this just returns the already existing scope variable but trigger an autorun every time it changes. a good example of this can be found it our tutorial.
Note: In anyway, when you use Tracker.autorun inside Angular, you need to connect it to a scope. this can be easily done if you replace Tracker.autorun with the $meteorUtils autorun function
Would be great if you could share a repo so that I can look on the broader perspective and could better determine what's the best solution from the two.
Based on another answer about "session" reacitivity, I could solve the problem. Just use the approach described in the link https://stackoverflow.com/a/21046935/4035797. You have to substitute Deps.autorun by Tracker.autorun though as Deps is deprecated and you have to make the scope variables for use in the template reactive (e.g., $scope.sentence_type = Session.getJSON("current_sentence.sentence_type");) and not the tokenizer.

Using waitfor from stratified in AngularJS

I'm using the StratifiedJS library's waitFor construct.
function myFunction() {
// some declarations
waitfor() {
AsyncService.getThisDone().then(function(result) {
// some more calculation
resume();
});
}
}
I am getting unexpected '{' on the line where waitfor is used as I cannot enclose the above code in <script type="text/sjs"> tag.
How can I get this working in AngularJS
The library you linked says you need to include this on the main page:
<script type="text/sjs">
// Your SJS code here!
</script>
Make sure you've labelled it that way. Most javascript is labelled as "text/javascript" (or has no label at all, in which case the <script> tag implies javascript.
The code you posted runs fine when typed in the command-line-like eval tool they have on the StratifiedJS page - likely there is nothing wrong with the code itself, just the way it's loaded.
As #blgt said, you need to put the SJS code inside an appropriate <script type="text/sjs"> tag (since most StratifiedJS code is not valid JavaScript).
In order to access SJS functions from plain JS frameworks like SJS, you could attach them to some well known object, e.g:
<script type="text/sjs">
window.myFunction = function() {
waitfor() {
AsyncService.getThisDone().then(function(result) {
// some more calculation
resume();
});
}
}
</script>
You can then call window.myFunction from any JS code. In real code you'd probably pick a more unique name ;)
Note: it's impossible to actually wait for the execution of a suspending StratifiedJS function like this if you call it from JS. So if you're calling StratifiedJS functions from JS, generally they should be fire-and-forget (i.e functions which are only executed for their side-effects, rather than functions which return a result).
This blog post has some details about integrating AngularJS and StratifiedJS, which may clarify the relation between them:
http://onilabs.com/blog/megazine
The short version is that your JavaScript syntax is invalid.
The medium version is that
functionCall() {
// stuff here
}
...is invalid. You'd need a ; after functionCall(), and if you had one, the block following it would have no purpose.
Given that your linked documentation suggests that it is valid, there must be some pre-processing step that turns the code described into valid JavaScript that you've skipped. So your code is being processed as normal JavaScript, in which this construct is not valid.
The solution is to ensure that this code is pre-processed and/or run in the right environment (I think it's by their Conductance server).

Angular translate extend existing translations

I am trying to have external modules change my $translateProvider.translation on the main module. see this as a "tranlation plugin" for my app.
it seems like changing translations from the $translate service is not possible.
mymodule.service('MyService', function ($translateProvider) {
var lib = function () {
//EDITED FOR BREVITY
this._registerTranslations = function (ctrl) {
if (!ctrl.i18n) return;
for (var name in ctrl.i18n) {
/////////////////////////////
// THIS IS THE PLACE, OBVIOUSLY PROVIDER IS NOT AVAILABLE!!!!
$translateProvider.translations(name, ctrl.i18n[name]);
//////////////////////////////
}
};
//EDITED FOR BREVITY
};
return new lib();
});
anyone with a bright idea?
So, to answer your question: there's no way to extend existing translations during runtime with $translate service without using asynchronous loading. I wonder why you want to do that anyway, because adding translations in such a way means that they are already there (otherwise you would obviously use asynchronous loading).
Have a look at the Asynchronous loading page. You can create a factory that will load a translation from wherever you want.
I created an Angular constant to hold new translations. If I want to add a new translation, I add it to the constant. Then in my custom loader, I first check the constant to see if the translation exists (either a new one, or an updated one). If so, I load it from the constant. If not, I load it from a .json file (or wherever you load your initial translations from). Use $translate.refresh() to force translations to be reloaded and reevaluated.
Demo here
The demo is pretty simple. You would need to do a little more work if you wanted to just change a subset of the translations, but you get the general idea.
From the AngularJS docs (https://docs.angularjs.org/guide/providers):
You should use the Provider recipe only when you want to expose an API for application-wide configuration that must be made before the application starts. This is usually interesting only for reusable services whose behavior might need to vary slightly between applications.
Providers are to be used with the application's .config function. $translateProvider for configuration, $translate for other services and controllers.

Resources