How to deal with Kendo MVC event binding when ES6 Modules loaded asyncronous by SystemJs - kendo-asp.net-mvc

As SystemJs loads modules async you can not bind events into the html file directly. Even when you declare the controls after System.import all functions are undefined at this time.
For example the following is not working:
<script type="text/javascript">
System.import('myModule'); //myModule contains a function called onClick.
</script>
#(Html.Kendo().Button()
.Name("RazorButton")
.Content("RazorButton")
.HtmlAttributes(new { type = "button" })
.Events(ev => ev.Click("onClick")))
<button id="html5Button">Html5 Button</button>
<script type="text/javascript">
$("#html5Button").kendoButton({
click: onClick
});
</script>
In this example onClick is allways undefined.
This is not really a problem with the html5 way as I can init the button into the module. With the MVC wrapper however it is the common way to init the button in here.
Is there a way to use the Kendo MVC wrapper in combination with SystemJs module laoding with no drawbacks?

I did not really found a solution with absolutly no drawback.
However, this solution works for me:
Ceate a methods bindEvents in your Module.
myModule.ts
export class MyModule {
bindEvents = () => {
$("#RazorButton").data("kendoButton").bind("click", this.buttonClick);
}
buttonClick = (e) => {
//do something...
}
}
Create a factory for your Module.
myModuleFactory.ts:
import { MyModule} from "./myModule";
export function Create(): MyModule{
return new MyModule();
}
Import the Factory into the View. Call the Create Methods from the factory and get MyModule as Return value. Then call bindEvents from MyModule.
view.cshtml:
<script type="text/javascript">
System.import('myModuleFactory').then(function (e) {
var vm = e.Create();
vm.bindEvents();
}).catch(console.error.bind(console));
</script>
The drawback in here is, that you need to know the name of the kendo event.

Related

Can't get to add JS to front via $this->load->view

I'm developing a module for OpenCart 3.x and I'm trying to insert some JavaScript code into website's front via $this->load->view() method but cannot get it to work, as the JS code doesn't appear in the DOM.
Here's an excerpt of my code:
/catalog/controller/extension/module/mymodule.php
class ControllerExtensionModuleMyModule extends Controller {
public function index() {
$this->load->language('extension/module/mymodule');
$this->load->model('checkout/order');
$this->load->model('setting/setting');
$this->load->model('design/layout');
$this->load->model('catalog/category');
$this->load->model('catalog/manufacturer');
$this->load->model('catalog/product');
$this->load->model('catalog/information');
$data['js_output'] = "Some JS output";
return $this->load->view('extension/module/mymodule', $data);
}
}
catalog/view/theme/default/template/extension/module/mymodule.twig
<script type="text/javascript">
console.log('This is working!");
</script>
Am I missing something?
Add an external javascript file via controller like this:
$this->document->addScript('catalog/view/javascript/my-external-script.js');
But if it's a block of javascript code, you don't need to edit controller file, just add it to your view file (twig or tpl):
<script type="text/javascript">
console.log("This is working!");
</script>
Finally you may need to clear caches (vqmod, ocmod, twig etc ...).
EDIT
If you want to prepare your javascript code in the controller, follow this:
controller
$data['js_output'] =
'<script type="text/javascript">
console.log("This is working!");
</script>';
Twig
{{ js_output }}
I have finally fixed the issue. It was a problem with the install() method not correctly inserting and loading the design/layout ($this->model_design_layout->getLayouts()).
If your module needs to insert a layout into the layout_module table, make sure you have the correct layout_id, code(which must be your module name), position (i.e: 'content_bottom') and sort_order(which often times is > 90).

Custom view helper in Ember.js, "You can't use appendChild outside of the rendering process"

I want to bind my custom view's class to a controller property.
[javascript]
App.IndexController = Ember.Controller.extend({
headerClass: "a"
});
App.TestHeaderView = Ember.View.extend({
classNames: ["test-header"],
classNameBindings: ["headerClass"],
headerClass: null,
templateName: "views/test-header"
});
[templates]
<script type="text/x-handlebars" data-template-name="index">
{{view App.TestHeaderView text="view helper" headerClass=controller.headerClass }}
<hr />
{{input value=headerClass}}
</script>
<script type="text/x-handlebars" data-template-name="views/test-header">
<small>{{view.text}}</small>
</script>
The result is predictable: everything works. I can enter the class name in the text box and see it reflected in the view.
So now I want to extend this and add my own helper that wraps the {{view}} call.
[javascript]
Ember.Handlebars.helper("test-header", function (options) {
return Ember.Handlebars.helpers.view.call(this, App.TestHeaderView, options);
});
[templates]
<script type="text/x-handlebars" data-template-name="index">
{{test-header text="custom helper" headerClass=controller.headerClass}}
</script>
Nothing special right? Except, I keep getting this:
Uncaught Error: You can't use appendChild outside of the rendering process
For full working jsbin, click here.
It seems this should work. I'm just wrapping the ember's view helper pretty much exactly. What am I missing?
I figured it out.
The trick is in the contexts array in the options hash.
When you call {{view App.MyView}} from handlebars, Ember's view helper gets in its options.contexts array the "context" in which it should search for "App.MyView" property - usually the current controller. In this case, "App.MyView" will be resolved regardless of the context, but I guess Ember keeps the context around and uses it to resolve bound properties.
When I called:
{{test-header text="custom helper" headerClass=controller.headerClass}}
there was no first argument from which to draw the context. Therefore, when I passed the call along to the view helper:
return Ember.Handlebars.helpers.view.call(this, App.TestHeaderView, options);
... there was no context passed along in the options.contexts array.
The way I fixed this is:
Ember.Handlebars.helper("test-header", function (options) {
options.contexts = [this].concat(options.contexts);
return Ember.Handlebars.helpers.view.call(this, App.TestHeaderView, options);
});
IMO Ember should do a better job here. They should either figure out a context from reference, or throw an error (a preferred option).

How to access the content of a controller based on a property name

I am trying to reuse code in my controllers / templates. In one of my templates, I have the following:
{{#if controllers.nodesIndex.content.isUpdating}}
This is working fine, but makes this template very specific. I want to generalize this. I would like to do somthing like:
{{#if controllers.{{indexController}}.content.isUpdating }}
Where indexController is actually a config parameter in my controller:
indexController : 'nodesIndex',
The syntax used is not working, and I do not know if there is a way to do what I am trying to do.
Another thing I have tried is simply:
{{#if isUpdating}}
And I have defined this as a computed property in the controler. This is the generic setup ("base class"):
getIndexController : function () {
return this.get('controllers.' + this.indexController);
},
isUpdating : function () { return this.getIndexController().get('content').isUpdating; }.property(),
But I am unable to tell property() which other properties this computed property depends on, since they are actually configurable, as returned by the getIndexController() function.
Any ideas on how to access the content of a "dynamic" controller?
I think that you are going to the wrong way. Ember provide some options to you share templates between controllers.
Here I made a example with partial
http://jsfiddle.net/marciojunior/KxM9k/
<script type="text/x-handlebars" data-template-name="application">
<h1>Partial example</h1>
{{#linkTo "index"}}Index{{/linkTo}}
{{#linkTo "other"}}Other{{/linkTo}}
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
<h2>Index template</h2>
{{partial "updatingMessage"}}
</script>
<script type="text/x-handlebars" data-template-name="other">
<h2>Other template</h2>
{{partial "updatingMessage"}}
</script>
All options avaliable are in that link http://emberjs.com/guides/templates/rendering-with-helpers/
Having a property on your controller that is the name of the "dynamic" controller is a good place to start. Why not just make your property depend on that?
getIndexController : function () {
return this.get('controllers.' + this.indexController);
},
isUpdating : function () {
return this.getIndexController().get('content').isUpdating;
}.property('indexController'),
If you think of "getIndexController" as a helper function then it makes sense. Rolling the two functions together makes it easier to see that the indexController attribute is the only real property that needs to be observed:
isUpdating : function () {
return this.get('controllers.' + this.indexController).get('content').isUpdating;
}.property('indexController'),
I have implemented this in an indirect way, with an observer:
isUpdatingChanged: function () {
this.getTopController().set('isUpdating', this.get('content').get('isUpdating'));
}.observes('content.isUpdating'),

Emberjs + Handlebars + Object onchange and events

I'm finding that jQuery observers aren't bound to elements that are not shown in handlebars logic.
Let's say I have the following;
{{#if person}}
Welcome back, <b>{{person.firstName}} {{person.lastName}}</b>!
{{else}}
Please <a class="login">log in</a>.
{{/if}}
<script>
$('.login').click(function() {
alert("Hi there.");
});
</script>
If I run in console, person = null (or whatever's needed to convince that person is empty) - the login observer doesn't work. I'm already using embers didInsertElement() to load a few other things, but is there a "onChange" event I can hook into so I can rebind event observers?
The big question is why you want that? Ember has excellent built in support for click handlers without going via jQuery. The reason your <script> is not working is likely to be down to the deferred way ember inserts views into the DOM. When you do Ember.View.append() the element is inserted in the DOM later.
That said, here's a fiddle that does what I think you want attaching the jQuery click handler in didInsertElement().
http://jsfiddle.net/algesten/5LPPz/1/
didInsertElement: function () {
// appending click handler directly with jQuery
$('.login').click(function() {
alert("Hi there.");
});
}
However the ember way would be to just use the click implicit handler function:
http://jsfiddle.net/algesten/5LPPz/2/
click: function () {
alert("Hi there.");
}
N.B. the latter handler attaches to the surrounding handlebar div and not the a, but clicks bubble.
The problem your facing is that javascript can only bind to elements that exist in the dom. Once you add a new element it wants you to re-bind those events. Luckily, jQuery is your friend on this one.
<script>
$('body').on('click', '.login', function() {
alert("Hi there.");
});
</script>
Ideally, your selector is the closest parent to .login that doesn't get added by javascript. The above is safe bet if you're not sure

Using an external Template in KnockoutJS

is it possible to use an external Template in KnockoutJS like this?
<script type="text/html" id="a_template" src="templates/a_template.html">
</script>
I've tried this solution but didn't get it working.
You can use jquery to dynamically load html into a script element, and then execute knockout based on that.
<script type="text/html" id="template_holder"></script>
<script type="text/javascript">
$('#template_holder').load('templates/a_template.html', function() {
alert('Load was performed.');
//knockout binding goes here
});</script>
Your knockout binding must be done in the callback function though, otherwise there's a chance that you'll be trying to bind before the page has loaded
UPDATE Here's an example I've coded on jsfiddle to demonstrate dynamic loading: http://jsfiddle.net/soniiic/2HxPp/
You could also look at:
https://github.com/ifandelse/Knockout.js-External-Template-Engine
You can also use this Template bootstrapper for KO
Bootstrapper
https://github.com/AndersMalmgren/Knockout.Bootstrap
MVC WebAPI Demo
https://github.com/AndersMalmgren/Knockout.Bootstrap.Demo
It uses a Convention over configuration approuch using this lib
https://github.com/AndersMalmgren/Knockout.BindingConventions
Meaning it will automatically understand that MyViewModel should be matched to MyView
Its also prepared to work nicely in a SPA
Disclaimer: I'm the author behind the 3 libs mentioned above
Here's a little function building off of soniiic's answer:
function loadExternalKnockoutTemplates(callback) {
var sel = 'script[src][type="text/html"]:not([loaded])';
$toload = $(sel);
function oncomplete() {
this.attr('loaded', true);
var $not_loaded = $(sel);
if(!$not_loaded.length) {
callback();
}
}
_.each($toload, function(elem) {
var $elem = $(elem);
$elem.load($elem.attr('src'), _.bind(oncomplete, $elem));
});
}
This'll automatically load all knockout templates on your document, provided their src is set and their type is "text/html". Pass in a callback to be notified when all templates loaded. No idea what happens if any of them fails.
Example usage:
<head>
<script type="text/html" src="kot/template1.html" id="template1"></script>
</head>
<body>
<script>
$(function() {
loadExternalKnockoutTemplates(function() {
// Put your ready code here, like
ko.applyBindings();
});
});
function loadExternalKnockoutTemplates(callback) {
var sel = 'script[src][type="text/html"]:not([loaded])';
$toload = $(sel);
function oncomplete() {
this.attr('loaded', true);
var $not_loaded = $(sel);
if(!$not_loaded.length) {
callback();
}
}
_.each($toload, function(elem) {
var $elem = $(elem);
$elem.load($elem.attr('src'), _.bind(oncomplete, $elem));
});
}
</script>
</body>