Aurelia - multiple Enhance statements - aurelia-templating

Updated with solution (28.03.2017):
http://aurelia.io/hub.html#/doc/article/aurelia/framework/latest/app-configuration-and-startup/8
Have updated Aurelia docs with solution (scroll down a little).
Special thanks to Charleh for hint.
Question:
Aurelia has this nice feature calls enhance, which can help you enhancing specific parts of your application with Aurelia functional.
But can we have multiple enhance statements on the same page? It seems problematical.
Example:
Task: enhance first component on the page, then get some data from the server and enhance second component on the page with server data as binding context
HTML
<!DOCTYPE html>
<html>
<head>
<title>Title</title>
</head>
<body>
<my-component1></my-component1>
<my-component2></my-component2>
</body>
</html>
JS
import { bootstrap } from 'aurelia-bootstrapper-webpack';
bootstrap(function(aurelia) {
aurelia.use
.standardConfiguration()
.globalResources("my-component1", "my-component2");
aurelia.start().then((app) => {
// Enhance first element
app.enhance(null, document.querySelector('my-component1'));
// Get some data from server and then enhance second element with binding context
getSomeDataFromServer().then((data) => {
app.enhance(data, document.querySelector('my-component2'));
});
});
});
Result:
In the result we will enhance first component, but when it's time for the second one, Aurelia will try to enhance first component one more time!
It happens because of aurelia-framework.js _configureHost method.
So basically when you start enhance it starts this method with your element as an application host:
Aurelia.prototype.enhance = function enhance() {
var _this2 = this;
var bindingContext = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
var applicationHost = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
this._configureHost(applicationHost || _aureliaPal.DOM.querySelectorAll('body')[0]);
return new Promise(function (resolve) {
var engine = _this2.container.get(_aureliaTemplating.TemplatingEngine);
_this2.root = engine.enhance({ container: _this2.container, element: _this2.host, resources: _this2.resources, bindingContext: bindingContext });
_this2.root.attached();
_this2._onAureliaComposed();
resolve(_this2);
});
};
And inside the _configureHost we can see this if statement which is just checking if our app instance is already host configured then do nothing.
Aurelia.prototype._configureHost = function _configureHost(applicationHost) {
if (this.hostConfigured) {
return;
}
...
Problem
So the actual problem here is that any enhanced element automatically became an application host (root) and when you try to enhance another element with the same aurelia instance you will just end up enhancing the first element always.
Question
Is this some way around for the cases when I want to enhance several elements on the page?

There's a clue here:
this.root = engine.enhance({container: this.container, element: this.host, resources: this.resources, bindingContext: bindingContext});
this.root.attached();
The aurelia.enhance just wraps the TemplatingEngine instance's .enhance method.
You could just pull TemplatingEngine from the container and call .enhance on it passing the bindingContext since aurelia.enhance does just that (but adds the additional "host configure" step that you've already done via your first .enhance call).
So that bit might look like:
import { Container } from 'aurelia-dependency-injection';
let engine = Container.instance.get(TemplatingEngine);
engine.enhance({ container: Container.instance, element: document.querySelect('my-component2'), resources: (you might need to inject these too), bindingContext: someContext });
(disclaimer: I didn't test the above so it may not be accurate - also you probably need to pass the resources object in - you can inject it or pull it from the container - I believe the type is just Resources)
However - something to note: your my-component2 won't actually be a child of your host element my-component1. I'm not sure if that will cause issues further down the line but it's just a thought.
I'm still curious as to why you'd want to bootstrap an Aurelia instance and then have it enhance multiple elements on the same page instead of just wrapping all that server response logic inside the component's viewmodel itself?
Maybe you can give a bit more context to the reason behind this?

My workaround for this issue for now (thanks to Charleh for the clue):
import { bootstrap } from 'aurelia-bootstrapper-webpack';
import {TemplatingEngine} from "aurelia-framework";
let enhanceNode = function (app, node, bindingContext = null) {
let engine = app.container.get(TemplatingEngine);
let component = engine.enhance({container: app.container, element: node, resources: app.resources, bindingContext: bindingContext});
component.attached();
}
bootstrap(function(aurelia) {
aurelia.use
.standardConfiguration()
.globalResources("my-component1", "my-component2")
aurelia.start().then((app) => {
enhanceNode(document.querySelector('my-component1'));
enhanceNode(document.querySelector('my-component2'));
});
});
That way you can skip host configuration for the app and can enhance as many custom elements as you want on the page.

Related

Vue 3 composable performance issues?

In the following example from the vue 3 documentation, we're creating a composable to track the mouse movement. https://vuejs.org/guide/reusability/composables.html#mouse-tracker-example
export function useMouse() {
const x = ref(0)
const y = ref(0)
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => {
console.log('mouse event listener bound')
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return { x, y }
}
And then using it within our components to retreive the mouse position on the page.
<script setup>
import { useMouse } from './mouse.js'
const { x, y } = useMouse()
</script>
<template>Mouse position is at: {{ x }}, {{ y }}</template>
While this works, what I find strange is that using this method you end up with event listeners being attached for each component. So if there are 100 components using useMouse I'll have 100 event listeners attached to the body, each performing the update function which, when added up with other composables, can soon become a performance bottleneck.
Why is the official documentation recommending using this method? Wouldn't a singleton be better suited to this use case? If so how would I create a singleton composable, the vue3 documentation doesn't seem to mention singletons at all.
The document shows that example for the practical purpose only. So the way you are using the composable decides the impact of it on the performance.
If you want to use its function on 100 components at the same time, yes, a singleton would be a good option. But if you want to use the composable in many pages that are separated by the vue router, the composable is totally fine.

React Admin page not rendering correctly

I am new to UI coding and started using react-admin for putting some simple pages. Everything went well and we are able to host pages correctly. But we have noticed random issues where the background image is filling up the entire screen or sometimes the whole page gets reduced to the hamburger menu. I have disabled the registerServiceWorker to stop having my pages in cache. Not sure if this is causing the weird UI behavior.
I don't know why you get those issues, the description is way too generic and it seems you don't have any idea what the problem can be, probably due to being new to the area. Either way the kind of problem you appear to have is probably related to CSS which is a way give style to your page. But React Admin doesn't use CSS directly, you can use it that way, but for more dynamic way to style the page the Material-ui library uses a thing called JSS to apply the styles.
There are many libraries that are being used together in order to produce React Admin, you should have an understanding of the most important ones in order to do something fancy. My advice to you since you are new, and you pretend to use React Admin, first use what React Admin offers and when you feel comfortable using that components and have a general grasp how the framework works, after that start implementing your own components that don't have a direct relation to React Admin but use some of the same libraries of React Admin.
Also check if you are creating a React Admin app using the <Admin> component or are embedding React Admin in another app since the second is more probable to produce bugs.
After some debugging, I think i figured out the cause of this issue. I had a custom button to duplicate a row (basically post a create and route to edit page on the new id). For some reason, the rendering of that button seems to have caused this issue inconsistently. The actual button works fine but causes this inconsistent behavior. Below is the code for that button. Is there any issue with the below?:
export default class DuplicateButton extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.state = ({ redirect: false });
var redirectPath = '';
}
handleClick = (props) => {
var
{
push, record, resourceName
} = this.props;
let tempRecord = record;
var result = '';
console.log(this.props);
var p = restDataProvider(CREATE, this.props.resource + "/" + tempRecord.id, { data: tempRecord }).then(resp => {
result = resp.data;
let routePath = '/' + this.props.resource + '/' + result.id;
console.log(routePath);
this.redirectPath = routePath;
this.setState({ redirect: true });
return result;
});
}
render() {
if (this.state.redirect) {
console.log('Redirect to Edit page');
return <Redirect push to={this.redirectPath} />;
}
return <Button variant="flat" color="primary" label="Duplicate Entry" onClick={this.handleClick}><DuplicateIcon /></Button>;
}
}

dynamic html into a view using ui router

Ok so i am not looking for an example more of help with an approach i am primarily a java developer so please excuse (and correct) the terminology if it need be. This is also why i need help as i am still early on into my journey into angular.
So i am using angular 5, along with ui-router. I am trying to design a three tabbed page [view, html, css] where the html and css will be text areas where a user will enter said thing, then , the view will be the rendering of that. There will be data (can be fetched prior to or at the time of rendering the view) that will bind to that html. The user will basically be putting in angular templates.
I have been reading this example but not sure if that is the proper approach.
this article had the solution
https://blog.angularindepth.com/here-is-what-you-need-to-know-about-dynamic-components-in-angular-ac1e96167f9e
basically it looks like this
#ViewChild("ancc", { read: ViewContainerRef }) container;
#Input() property:Property = new Property();
constructor(private resolver: ComponentFactoryResolver,private _compiler: Compiler){
console.log("hit layout constructor");
}
view(){
// create the template
const template = '<span>generated on the fly: {{property.label}}</span>';
//clear out the old instance
this.container.clear();
const tmpCmp = Component({template: template})(class {
});
const tmpModule = NgModule({declarations: [tmpCmp]})(class {
});
this._compiler.compileModuleAndAllComponentsAsync(tmpModule)
.then((factories) => {
const f = factories.componentFactories[0];
//attach the component to the view
const cmpRef = this.container.createComponent(f);
//bind the data
cmpRef.instance.property = this.property;
})
}
hope this helps someone!

Is Backbone.js suitable for getting HTML from server?

As far as I can tell, Backbone.js view represents DOM element. I take it from existing DOM or create it on the fly in el attribute.
In my case, I want to take it from server with AJAX request because I'm using Django templates and don't want to rewrite everything to JavaScript templates.
So I define el function that performs AJAX request.
el: function() {
model.fetch().success(function(response) {
return response.template
})
}
Of course, it does NOT work because AJAX request is executed asynchronous.
This means that I don't have el attribute and events does NOT work neither. Can I fix it?
Maybe the Backbone.js framework isn't the right tool for my needs? The reason I want to use that was to have some structure for the code.
P.S. I'm new to Backbone.js.
Do your ajax request from another view, or directly after the page load using jquery directly, and after you've downloaded your template, THEN instantiate your backbone view class with the proper id/el or whatever (depending on where you stored your ajax fetched template). Depending on your use-case, this may or may not be a sensible approach.
Another, perhaps more typical approach, would be to set up your view with some placeholder element (saying "loading" or whatever), then fire off the ajax, and after the updated template has been retrieved, then update your view accordingly (replace the placeholder with the actual template you requested).
When/if you update your view with new/other DOM elements, you need to call the view's delegateEvents method to rebind your events to the new elements, see:
http://backbonejs.org/#View-delegateEvents
I came across a similar requirement. In my instance, I was running asp.net and wanted to pull my templates from user controls. The first thing I would recommend is looking into Marionette because it will save you from writing a lot of boiler plate code in Backbone. The next step is to override how your templates are loaded. In this case I created a function that uses Ajax to retrieve the HTML from the server. I found an example of this function where they were using it to pull down html pages so I did a little modification so I can make MVC type requests. I can't remember where I found the idea from; otherwise, I would give the link here.
function JackTemplateLoader(params) {
if (typeof params === 'undefined') params = {};
var TEMPLATE_DIR = params.dir || '';
var file_cache = {};
function get_filename(name) {
if (name.indexOf('-') > -1) name = name.substring(0, name.indexOf('-'));
return TEMPLATE_DIR + name;
}
this.get_template = function (name) {
var template;
var file = get_filename(name);
var file_content;
var result;
if (!(file_content = file_cache[name])) {
$.ajax({
url: file,
async: false,
success: function (data) {
file_content = data; // wrap top-level templates for selection
file_cache[name] = file_content;
}
});
}
//return file_content.find('#' + name).html();
return file_content;
}
this.clear_cache = function () {
template_cache = {};
};
}
The third step would be to override Marionette's method to load templates. I did this in the app.addInitializer method. Here I am initializing my template loader and setting it's directory to a route handler. So when I want to load a template, I just set the template: "templatename" in my view and Backbone will load the template from api/ApplicationScreens/templatename. I am also overriding my template compiling to use Handlebars because ASP.net is not impressed with the <%= %> syntax.
app.JackTemplateLoader = new JackTemplateLoader({ dir: "/api/ApplicationScreens/", ext: '' });
Backbone.Marionette.TemplateCache.prototype.loadTemplate = function (name) {
if (name == undefined) {
return "";
} else {
var template = app.JackTemplateLoader.get_template(name);
return template;
}
};
// compiling
Backbone.Marionette.TemplateCache.prototype.compileTemplate = function (rawTemplate) {
var compiled = Handlebars.compile(rawTemplate);
return compiled;
};
// rendering
Backbone.Marionette.Renderer.render = function (template, data) {
var template = Marionette.TemplateCache.get(template);
return template(data);
}
Hopefully this helps. I've been working on a large dynamic website and it is coming along very nicely. I am constantly being surprised by the overall functionality and flow of using Marionette and Backbone.js.

MVC2 Client-Side Validation for injected Ajax content

I am making an Ajax call and adding content to a form inside a MVC2 app.
I need to update the Client Validation Metadata with the validation for my new content.
<script type="text/javascript">
//<![CDATA[
if (!window.mvcClientValidationMetadata) { window.mvcClientValidationMetadata = []; }
window.mvcClientValidationMetadata.push({"Fields":[{"
...
</script>
Is there a way to generate this metadata for a partial view ?
Thanks in advance.
I was banging my head against a wall for a few days on this too and was going to go down the route of removing the form tag, but have just got it working in a slightly less hacky way if you are still interested. My scenario was similar in that I have a form with a collection of elements to validate initially, but users can dynamically add new rows via ajax.
I'll break it down so hopefully it'll be easier to see what is going on. Looking at the MVC source code, the form and validation works roughly as so:
Html.BeginForm() outputs the opening form tag then creates and returns a new instance of MvcForm, which doesn't outwardly do much except make the scope of the form easier to manage for you.
It does however create a new FormContext and stores this within ViewContext.FormContext. It is this FormContext that tracks the client validation.
The last thing Html.BeginForm() does is set the FormId property of the new FormContext, using the id of the form tag. This is required so the client script can match up forms and validation rules.
Html.EndForm() disposes the MvcForm. This Dispose method outputs the form closing tag and then calls ViewContext.OutputClientValidation() which is resposible for outputting the javascript. Lastly it removes the current FormContext and sets it back to the parent FormContext or null if there isn't one.
So to not output the form tag we somehow need to take some of the FormContext management out of the MvcForm constructor/destructor.
So within my Partial View I did the following:
At the top I check if the ViewContext.FormContext has a value. If so we we are in the initial load so no need to mess around. If not, it is an ajax call, so I enable client validation, create a new MvcForm directly (not with BeginForm) - this causes a FormContext to be created - and set the FormContext.FormId to the same as my parent page
At the end of the view, I check if I have a form instance and if so, call ViewContext.OutputClientValidation() and reset the ViewContext.FormContext to null. I do not Dispose() the MvcForm as this would output the closing tag and MvcForm does not contain disposable objects.
The skeleton of the view looks as so:
<%
MvcForm dummyForm = null;
if (this.ViewContext.FormContext == null)
{
Html.EnableClientValidation();
dummyForm = new MvcForm(this.ViewContext);
this.ViewContext.FormContext.FormId = "mainform";
}
%>
// standard partial view markup goes here
<%
if (dummyForm != null)
{
this.ViewContext.OutputClientValidation();
this.ViewContext.FormContext = null;
}
%>
You could quite easily wrap this up into an extension method
Phil
Finally got it to work.
The answer is simple: don't waist time with MicrosoftMvcValidation.js. It is generated with Script# which makes it difficult to extend.
Switch to xVal and jQuery Validation.
It doesn't need a form to generate the client validation metadata.
Also in order to load validation for a AJAX request all you have to do is to call the following after you have the new Html:
lForm.find("#placeholder").empty();
lForm.valid();
lForm.find("#placeholder").html(responseHtml);
That does it. First you remove the old content. Than re-run validation to get rid of potentially obsolete validation errors. Than add the new content. Works like a cham.
Also jQuery Validation makes it really easy to enable or disable validation for a certain field (conditional validation).
I have the same problem and resolve using the Future files, and in MicrosoftMvcJQueryValidation.js I change the and of file, this:
$(document).ready(function () {
var allFormOptions = window.mvcClientValidationMetadata;
if (allFormOptions) {
while (allFormOptions.length > 0) {
var thisFormOptions = allFormOptions.pop();
__MVC_EnableClientValidation(thisFormOptions);
}
}
});
for:
function chargeValidation() {
var allFormOptions = window.mvcClientValidationMetadata;
if (allFormOptions) {
while (allFormOptions.length > 0) {
var thisFormOptions = allFormOptions.pop();
__MVC_EnableClientValidation(thisFormOptions);
}
}
}
and in content after close form using I call the 'chargeValidation()', this resolve for me the problem I have using $.get(action) containing a form validation.
I hope to help you!
Finally found it.
After content is loaded in dynamically you will need to register the new form.
Since I am using Facebox, I added it to the facebox code, however you can add it wherever you need, or in a callback if your modal or whatever you are loading into has an afterLoaded event.
I wrapped them in a try/catch just in case i ever use facebox without the validation stuff.
Just run these two lines AFTER your content has been loaded:
try {
Sys.Application.remove_load(arguments.callee);
Sys.Mvc.FormContext._Application_Load();
} catch (err) {/* MVC Clientside framework is likely not loaded*/ }
I made some progress but I am not quite happy.
Problem #1: The client validation metadata is not generated unless you have a Html.BeginForm() in your partial. Which in my case is false because I do not update the entire form, I update portions of it.
Solution for Problem #1: Add a form in the partial view, let MVC generate the client validation MetaData and remove the form tags with a action filter. Let's call this Hack #1.
public class RemoveFormFilterAttribute : ActionFilterAttribute
{
private static readonly MethodInfo SwitchWriterMethod = typeof(HttpResponse).GetMethod("SwitchWriter", BindingFlags.Instance | BindingFlags.NonPublic);
private TextWriter _OriginalWriter;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
_OriginalWriter = (TextWriter)SwitchWriterMethod.Invoke(HttpContext.Current.Response, new object[] {new HtmlTextWriter(new StringWriter())});
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (_OriginalWriter != null)
{
HtmlTextWriter lTextWriter =(HtmlTextWriter) SwitchWriterMethod.Invoke(HttpContext.Current.Response, new object[] {_OriginalWriter});
string lOriginalHTML = lTextWriter.InnerWriter.ToString();
string lNewHTML = RemoveFormTags(lOriginalHTML);
filterContext.HttpContext.Response.Write(lNewHTML);
}
}
Problem #2: The initial client validation metadata for the page is gone by the time I have the metaData for the new content.
Solution for Problem #2: Store the initial metadata (hard copy) and update it with the new fieds, than call the methods you mentioned to let MVC know new stuff arrived. Let's call this Hack #2.
<script type="text/javascript">
var pageMvcClientValidationMetadata;
$(document).ready(function() {
$("input[name='PaymentTypeName']").change(PaymentTypeChanged);
//create a back-up of the ValidationMetadata
pageMvcClientValidationMetadata = JSON.parse(JSON.stringify(window.mvcClientValidationMetadata));
});
function PaymentTypeChanged() {
var selectedPaymentType = $("input[name='PaymentTypeName']:checked").val();
$.ajax(
{
url: 'PersonalData/GetPaymentTypeHtml?&paymentType=' + selectedPaymentType,
type: "GET",
cache: false,
success: GetPaymentTypeHtml_Success
});
}
function GetPaymentTypeHtml_Success(result) {
$('#divPaymentTypeDetails').html(result);
UpdateValidationMetaData();
}
function UpdateValidationMetaData() {
//update the ValidationMetadata
for (i = 0; i < window.mvcClientValidationMetadata[0].Fields.length; i++) {
pageMvcClientValidationMetadata[0].Fields.push(window.mvcClientValidationMetadata[0].Fields[i]);
}
//restore the ValidationMetadata
window.mvcClientValidationMetadata = JSON.parse(JSON.stringify(pageMvcClientValidationMetadata));
//Notify the Validation Framework that new Metadata exists
Sys.Application.remove_load(arguments.callee);
Sys.Mvc.FormContext._Application_Load();
}
Now. Any improvements would be appreciated.
Hack #1: How can I generate the client validation metadata without having an actual form ?
HAck #2: How can I appent to the page validation metadata ?

Resources