I've been trying to use dojo.require(If) with a local variable to dynamically load a module on a page based on a condition.
// note: dojo v1.4
djConfig = {
debugAtAllCosts: true
};
Example 1 (does not work):
(function() {
var nameOfClass = "Two";
dojo.require("my.namespace." + nameOfClass);
dojo.addOnLoad(function() {
var oneOrTwo = new my.namespace[nameOfClass]();
});
}());
Error: ReferenceError: nameOfClass is not defined.
Example 2 (does not work):
(function() {
var nameOfClass = "Two";
dojo.requireIf(nameOfClass == "One", "my.namespace.One");
dojo.requireIf(nameOfClass == "Two", "my.namespace.Two");
dojo.addOnLoad(function() {
var oneOrTwo = new my.namespace[nameOfClass]();
});
}());
Error: ReferenceError: nameOfClass is not defined.
Example 3 (works):
(function() {
window.nameOfClass = "Two";
dojo.requireIf(window.nameOfClass == "One", "my.namespace.One");
dojo.requireIf(window.nameOfClass == "Two", "my.namespace.Two");
dojo.addOnLoad(function() {
var oneOrTwo = new my.namespace[nameOfClass]();
});
}());
For some reason, it appears as though require and requireIf only allow global variables inside them. Is that a current limitation, or am I just doing something wrong?
Update 1:
Therefore, if I understand you (#Maine, #jrburke) correctly, this is a limitation of the debugAtAllCosts? If the above code is built as cross-domain (adding the xd file prefix / suffix) and is executed -- it will work as expected?
If that is the case, then what is the proper way of locally testing code that will be executed as cross-domain, without making the actual build?
That also makes me question the motivation for pre-parsing the dojo.require(s). If the loader_xd will not (or rather can not) pre-parse, why is the method that was created for testing/debugging doing so?
Update 2:
Since the two questions in the Update 1 above are not closely related to this one, I've moved them out into a separate discussion.
This is because requireIfs are parsed with regexps as the very first thing, and executed before the normal program flow.
If you'll grep Dojo source for requireIf, you should find this kind of lines handling it (loader_xd.js):
var depRegExp = /dojo.(require|requireIf|provide|requireAfterIf|platformRequire|requireLocalization)\s*\(([\w\W]*?)\)/mg;
The condition is then executed with eval in global scope, and not as a part of normal flow.
To clarify more of what Main said, this is an issue with the XD loader in Dojo. debugAtAllCosts: true uses the XD Loader. If you just use the normal Dojo loader without debugAtAllCosts, it is not an issue. Also, attaching the module module name as a property on a publicly visible module would also avoid the issue.
Related
I am experiencing the dreaded not an "own property" of its parent issue when attempting to build my Handlebars project.
I have been down the rabbit hole and seen the many explanations of using #handlebars/allow-prototype-access to allow the issue to be bypassed, however it seems the project does not use a standard implementation of Handlebars...
It seems I am using something called engine-handlebars
Where I would expect to implement that allow-prototype-access change, I see the following:
app.pages('./source/pages/**/*.hbs');
app.engine('hbi', require('engine-handlebars'));
I can't fathom how I am supposed to implement the prototype access with this setup...
It seems, after a bit of trial and error, commenting lines out as I go, that the line app.pages('./source/pages/**/*.hbs'); is actually causing the issue...
When I run the project with this line in, I get the error:
Handlebars: Access has been denied to resolve the property "path" because it is not an "own property" of its parent.
You can add a runtime option to disable the check or this warning:
See https://handlebarsjs.com/api-reference/runtime-options.html#options-to-control-prototype-access for details
[10:54:49] ERROR - undefined: Cannot read property 'substring' of undefined
The plugin #handlebars/allow-prototype-access works by modifying the Handlebars instance.
const _Handlebars = require('handlebars');
const { allowInsecurePrototypeAccess } = require('#handlebars/allow-prototype-access');
const Handlebars = allowInsecurePrototypeAccess(_Handlebars);
Note that allowInsecurePrototypeAccess does not modify the instance in place, but creates an isolated instance via Handlebars.create() so you must use its return value.
In your case, engine-handlebars exposes the Handlebars instance in different ways depending on what version you are using.
Based on your code you provided, my guess is you are using <1.0.0, but I'll provide methods for adjusting this for all its versions.
engine-handlebars#<0.6.0
Unfortunately these versions don't expose Handlebars in any way, so if you are using this version I recommend upgrading engine-handlebars to a later version.
engine-handlebars#>=0.6.0 <1.0.0
Version 0.6.0 exposed Handlebars as a property on the exported engine function. This is then referenced throughout the library via this.Handlebars.
You can then change this before setting the app.engine() and it should work.
const _Handlebars = require('handlebars');
const { allowInsecurePrototypeAccess } = require('#handlebars/allow-prototype-access');
const engine = require('engine-handlebars');
// elsewhere...
// const app = ...
// Do this *before* setting app.engine
const insecureHandlebars = allowInsecurePrototypeAccess(_Handlebars);
engine.Handlebars = insecureHandlebars;
app.engine('hbi', engine);
engine-handlebars#>=1.0.0
For version 1.0.0 and beyond, you must pass the Handlebars instance yourself.
const Handlebars = require('handlebars');
const engine = require('engine-handlebars')(Handlebars);
Thus you don't need to set anything on engine, you just pass in the modified instance when you need it.
const _Handlebars = require('handlebars');
const { allowInsecurePrototypeAccess } = require('#handlebars/allow-prototype-access');
// elsewhere...
// const app = ...
// Do this *before* setting app.engine
const insecureHandlebars = allowInsecurePrototypeAccess(_Handlebars);
const engine = require('engine-handlebars')(insecureHandlebars);
app.engine('hbi', engine);
Is there a way to set the context of the expression in Dynamic Expresso library, so that we can do something like the following:
interpreter.Eval("FirstName", new Parameter("person", new { FirstName="Homer", LastName="Simpson"}));
rather than
interpreter.Eval("person.FirstName", new Parameter("person", new { FirstName="Homer", LastName="Simpson"}));
Maybe we could have a another option that would say that the first parameter is to be used as the context for the expression.
I guess there could also be another version of Parse and Eval methods that simply takes the expression text and a simple object value that will serve as the expression context.
Other than that and the lack of support for dynamic types, I am really liking this library. I had worked on something similar, but had not added support for extension methods and generic method calls.
Thanks for the great library,
Neal
There isn't a built-in solution but you can simulate it in many ways:
Option 1: Inject an expression
var workingContext = new { FirstName = "homer" };
var workingContextExpression = Expression.Constant(workingContext);
var firstNameExpression = Expression.Property(workingContextExpression, "FirstName");
var interpreter = new Interpreter();
interpreter.SetExpression("FirstName", firstNameExpression);
Assert.AreEqual(workingContext.FirstName, interpreter.Eval("FirstName"));
Basically I inject an expression using SetExpression method. The injected expression is the property that you want to be available.
Option 2: Use this/me/it variable
You can inject a variable that will contain your working object. I usually call it this (or me or it depending on the application).
var workingContext = new { FirstName = "homer" };
var interpreter = new Interpreter();
interpreter.SetVariable("this", workingContext);
Assert.AreEqual(workingContext.FirstName, interpreter.Eval("this.FirstName"));
Option 3: A combination of the previous solutions
var workingContext = new { FirstName = "homer" };
var interpreter = new Interpreter();
interpreter.SetVariable("this", workingContext);
var firstNameExpression = interpreter.Parse("this.FirstName").LambdaExpression.Body;
interpreter.SetExpression("FirstName", firstNameExpression);
Assert.AreEqual(workingContext.FirstName, interpreter.Eval("FirstName"));
Equal to the first solution but I generate the expression using the parser itself.
Consider that all solutions assume that you must have an Interpreter instance for each context.
Disclaimer: I'm the author of Dynamic Expresso library.
Starting with DynamicExpresso v2.13.0, it's possible to define a variable named "this", that will be used for implicit resolution:
var target = new Interpreter();
target.SetVariable("this", new { FirstName="Homer", LastName="Simpson"});
// 'this' variable is used implicitly
Assert.AreEqual("Homer", target.Eval("FirstName"));
// 'this' variable can also be used explicitly
Assert.AreEqual("Homer", target.Eval("this.FirstName"));
I have a table which gets its data server-side, using custom server-side initialization params which vary depending upon which report is produced. Once the table is generated, the user may open a popup in which they can add multiple additional filters on which to search. I need to be able to use the same initialization params as the original table, and add the new ones using fnServerParams.
I can't figure out how to get the original initialization params using the datatables API. I had thought I could get a reference to the object, get the settings using fnSettings, and pass those settings into a new datatables instance like so:
var oSettings = $('#myTable').dataTable().fnSettings();
// add additional params to the oSettings object
$('#myTable').dataTable(oSettings);
but the variable returned through fnSettings isn't what I need and doesn't work.
At this point, it seems like I'm going to re-architect things so that I can pass the initialization params around as a variable and add params as needed, unless somebody can steer me in the right direction.
EDIT:
Following tduchateau's answer below, I was able to get partway there by using
var oTable= $('#myTable').dataTable(),
oSettings = oTable.fnSettings(),
oParams = oTable.oApi._fnAjaxParameters(oSettings);
oParams.push('name':'my-new-filter', 'value':'my-new-filter-value');
and can confirm that my new serverside params are added on to the existing params.
However, I'm still not quite there.
$('#myTable').dataTable(oSettings);
gives the error:
DataTables warning(table id = 'myTable'): Cannot reinitialise DataTable.
To retrieve the DataTables object for this table, please pass either no arguments
to the dataTable() function, or set bRetrieve to true.
Alternatively, to destroy the old table and create a new one, set bDestroy to true.
Setting
oTable.bRetrieve = true;
doesn't get rid of the error, and setting
oSettings.bRetrieve = true;
causes the table to not execute the ajax call. Setting
oSettings.bDestroy = true;
loses all the custom params, while setting
oTable.bDestroy = true;
returns the above error. And simply calling
oTable.fnDraw();
causes the table to be redrawn with its original settings.
Finally got it to work using fnServerParams. Note that I'm both deleting unneccessary params and adding new ones, using a url var object:
"fnServerParams": function ( aoData ) {
var l = aoData.length;
// remove unneeded server params
for (var i = 0; i < l; ++i) {
// if param name starts with bRegex_, sSearch_, mDataProp_, bSearchable_, or bSortable_, remove it from the array
if (aoData[i].name.search(/bRegex_|sSearch_|mDataProp_|bSearchable_|bSortable_/) !== -1 ){
aoData.splice(i, 1);
// since we've removed an element from the array, we need to decrement both the index and the length vars
--i;
--l;
}
}
// add the url variables to the server array
for (i in oUrlvars) {
aoData.push( { "name": i, "value": oUrlvars[i]} );
}
}
This is normally the right way to retrieve the initialization settings:
var oSettings = oTable.fnSettings();
Why is it not what you need? What's wrong with these params?
If you need to filter data depending on your additional filters, you can complete the array of "AJAX data" sent to the server using this:
var oTable = $('#myTable').dataTable();
var oParams = oTable.oApi._fnAjaxParameters( oTable );
oParams.push({name: "your-additional-param-name", value: your-additional-param-value });
You can see some example usages in the TableTools plugin.
But I'm not sure this is what you need... :-)
The problem, essentially, is that I can't get my sitemap config to support multiple sitemaps. It's always looking for "default" even when I name my instances and request another. Now for the background.
I've been pouring over the docs for the new implementation of MVCSiteMapProvider. They are now using Dependency Injection to configure the SiteMapProvider. We have an existing StructureMap DI implementation, so I followed the instructions and added, in our case
ObjectFactory.Configure(x =>
{
...
x.AddRegistry<MvcSiteMapProviderRegistry>();
...
});
Then I started tweaking the MvcSiteMapProviderRegistry.cs file to implement my multiple sitemap scenario. I have multiple site map files, either will work as long as it's called "default". If I remove the "default" item then it breaks and complains that "default" is missing. Which I assume is because it can't find my instance. Here's how I have them defined. I suspect the problem is somewhere in here... the loader which it says I have to configure in the Global.asax is looking for ISiteMapLoader but I'm adding my multiple configuration to SiteMapBuilderSet... anyway here's the code.
// Register the sitemap builder
string absoluteFileName = HostingEnvironment.MapPath("~/Main.sitemap");
string absoluteFileName2 = HostingEnvironment.MapPath("~/Test.sitemap");
var xmlSource = this.For<IXmlSource>().Use<FileXmlSource>()
.Ctor<string>("fileName").Is(absoluteFileName);
var reservedAttributeNameProvider = this.For<ISiteMapXmlReservedAttributeNameProvider>()
.Use<SiteMapXmlReservedAttributeNameProvider>()
.Ctor<IEnumerable<string>>("attributesToIgnore").Is(new string[0]);
var builder = this.For<ISiteMapBuilder>().Use<CompositeSiteMapBuilder>()
.EnumerableOf<ISiteMapBuilder>().Contains(y =>
{
y.Type<XmlSiteMapBuilder>()
.Ctor<ISiteMapXmlReservedAttributeNameProvider>().Is(reservedAttributeNameProvider)
.Ctor<IXmlSource>().Is(xmlSource);
y.Type<ReflectionSiteMapBuilder>()
.Ctor<IEnumerable<string>>("includeAssemblies").Is(includeAssembliesForScan)
.Ctor<IEnumerable<string>>("excludeAssemblies").Is(new string[0]);
y.Type<VisitingSiteMapBuilder>();
});
var xmlSource2 = this.For<IXmlSource>().Use<FileXmlSource>()
.Ctor<string>("fileName").Is(absoluteFileName2);
var builder2 = this.For<ISiteMapBuilder>().Use<CompositeSiteMapBuilder>()
.EnumerableOf<ISiteMapBuilder>().Contains(y =>
{
y.Type<XmlSiteMapBuilder>()
.Ctor<ISiteMapXmlReservedAttributeNameProvider>().Is(reservedAttributeNameProvider)
.Ctor<IXmlSource>().Is(xmlSource2);
y.Type<ReflectionSiteMapBuilder>()
.Ctor<IEnumerable<string>>("includeAssemblies").Is(includeAssembliesForScan)
.Ctor<IEnumerable<string>>("excludeAssemblies").Is(new string[0]);
y.Type<VisitingSiteMapBuilder>();
});
// Configure the builder sets
this.For<ISiteMapBuilderSetStrategy>().Use<SiteMapBuilderSetStrategy>()
.EnumerableOf<ISiteMapBuilderSet>().Contains(x =>
{
/* x.Type<SiteMapBuilderSet>()
.Ctor<string>("instanceName").Is("default")
.Ctor<bool>("securityTrimmingEnabled").Is(securityTrimmingEnabled)
.Ctor<bool>("enableLocalization").Is(enableLocalization)
.Ctor<ISiteMapBuilder>().Is(builder)
.Ctor<ICacheDetails>().Is(cacheDetails);*/
/*
x.Type<SiteMapBuilderSet>()
.Ctor<string>("instanceName").Is("MainSiteMapProvider")
.Ctor<bool>("securityTrimmingEnabled").Is(securityTrimmingEnabled)
.Ctor<bool>("enableLocalization").Is(enableLocalization)
.Ctor<ISiteMapBuilder>().Is(builder)
.Ctor<ICacheDetails>().Is(cacheDetails);*/
x.Type<SiteMapBuilderSet>()
.Ctor<string>("instanceName").Is("TestSiteMapProvider")
.Ctor<bool>("securityTrimmingEnabled").Is(securityTrimmingEnabled)
.Ctor<bool>("enableLocalization").Is(enableLocalization)
.Ctor<ISiteMapBuilder>().Is(builder2)
.Ctor<ICacheDetails>().Is(cacheDetails);
});
In my global.asax.cs I added
MvcSiteMapProvider.SiteMaps.Loader = Resolver.Get<ISiteMapLoader>();
and to reference in my view I have
#Html.MvcSiteMap("TestSiteMapProvider").Menu(false, true, true)
but it must not be able to find "TestSiteMapProvider" because it always displays "default" or complains if it doesn't exist.
I also thought it might have something to do with the Cache, as I see the filename referenced there, but I don't know how to add multiple instances to the cache, so I just disabled it. I'm really not doing anything fancy with my sitemaps anyway, and this whole thing is really feeling like massive overkill just to get some flippin automatic breadcrumbs!
Apparently there was another help doc that I wasn't aware of. I had completed all of the steps thus far properly, but I also needed to implement ISiteMapCacheKeyGenerator.
See this doc (which wasn't named this when I started.)
https://github.com/maartenba/MvcSiteMapProvider/wiki/Multiple-Sitemaps-in-One-Application
I'm trying to use a single controller to list multiple similar collections so I can call different templates with the same controller. In fact, right now I have 6 controllers for listing and another 6 for forms but they're all duplicates.
I've made a non-functional plunker just to show how I intend it to work. I've avoided declaring routeProviders because knowing it wouldn't work I tried to make it as straight to the point as I could.
http://plnkr.co/edit/d06PcrJS5newhrmNy6EJ?p=preview
I've seen on stackoverflow how to declare a class with a dynamic name:
var str = "MyClass";
var obj = new window[str];
But as I have not been able to find where it's stored I'm not able to retrieve it.
Does anyone have a hint on how to do this?
You can use Angular's injector to return the service instance you want. For example:
app.controller('NodeListCtrl', function($scope, $location, $injector) {
var modelName = $location.path().split("/")[1];
$scope.modelName = modelName.charAt(0).toUpperCase() + modelName.slice(1);
$scope.nodes = $injector.get($scope.modelName).query();
});
Note: Don't forget to add the $injector to the controller's function signature.
jsfiddle: http://jsfiddle.net/bmleite/Mvk2y/