An aesthetic or correct way of invoking an action that requires DOM manipulation as well as controller related tasks in EmberJS - view

My EmberJS app has a couple of actions (triggered by buttons) that require a view/DOM manipulation as well as setting a state in the controller, followed by a model update. The way I do this, it does not appeal to my programming aesthetics. It gets the job done, but it doesn't look good :(
Here is a gist of how I do things :
<button {{action 'whatever' target='view'}}></button>
App.MyView = Ember.View.extend({
actions:{
whatever:function(){
var ctrl = this.get('controller');
ctrl.set('property',value); // arbitrary example of setting a controller property through it's view
ctrl.controllerMethod(); // invoking a controller method through the view
**// do some DOM manipulation**
}
}
});
Naturally, I can wrap whatever controller related steps I am performing in the view in a controller method and invoke that method through the view, but IMO that's just equally ugly. The view shouldn't really be invoking a controller method like how I have done. Unfortunately, this specific action requires a DOM manipulation as well as setting some state and performing an action in the controller.
I am not sure what is the recommended way of performing this. Can anyone enlighten ?

I suggest you handle the action from the controller. I noticed you're setting a property. You can use that to signal something to the view and then do the DOM manipulation within the view.
App.MyView = Ember.View.extend({
function () {
**// do some DOM manipulation**
}.observe('controller.property');
});
The way I think about it is that UI 'actions' are mapped to a business event (e.g. addClient instead of click), then as a result of that something happens that could change properties of the model, controller. As a result of those changes the view might need to update directly, ideally through a binding, but sometimes is needed to modify the DOM manually.

as #LukeMelia said in his comment you should really handle the changes in your controller and update your view (if you need to?) via databinding.
so, you would just omitt the target="view" argument from your view helper and Ember will look for the proper action in the nearest controller, bubbling all the way up to the route, and so on.
a simple code snippet (with what you provided in your first post) would look like:
Handlebars Template:
<button {{action someAction}}>Fire!</button>
Ember.Controller:
App.MyController = Ember.ObjectController.extend({
myProperty: 'cool',
printMyCoolness: function () {
console.log("I'm using Ember.js!");
},
actions: {
someAction: function () {
this.set('myProperty', 'set on fire!');
this.printMyCoolness();
}
}
});

Related

Dynamically adding custom elements to DOM Aurelia [duplicate]

It seems Aurelia is not aware when I create and append an element in javascript and set a custom attribute (unless I am doing something wrong). For example,
const e = document.createElement('div');
e.setAttribute('custom-attr', 'some value');
body.appendChild(e);
Is there a way to make Aurelia aware of this custom attribute when it gets appended?
A little background: I am creating an app where the user can select their element type (e.g. input, select, checkbox etc.) and drag it around (the dragging is done in the custom attribute). I thought about creating a wrapper <div custom-attr repeat.for="e of elements"></div> and somehow render the elements array, but this seemed inefficient since the repeater will go through all the elements everytime I push a new one and I didn't not want to create a wrapper around something as simple as a text input that might be created.
You would have to manually trigger the Aurelia's enhance method for it to register the custom attributes or anything Aurelia related really. And you also have to pass in a ViewResources object containing the custom attribute.
Since this isn't as straight forward as you might think, I'll explain it a bit.
The enhance method requires the following parameters for this scenario:
Your HTML as plain text (string)
The binding context (in our scenario, it's just this)
A ViewResources object that has the required custom attribute
One way to get access to the ViewResources object that meets our requirements, is to require the custom attribute into your parent view and then use the parent view's ViewResources. To do that, require the view inside the parent view's HTML and then implement the created(owningView, thisView) callback in the controller. When it's fired, thisView will have a resources property, which is a ViewResources object that contains the require-d custom attribute.
Since I am HORRIBLE at explaining, please look into the example provided below.
Here is an example how to:
app.js
import { TemplatingEngine } from 'aurelia-framework';
export class App {
static inject = [TemplatingEngine];
message = 'Hello World!';
constructor(templatingEngine, viewResources) {
this._templatingEngine = templatingEngine;
}
created(owningView, thisView) {
this._viewResources = thisView.resources;
}
bind() {
this.createEnhanceAppend();
}
createEnhanceAppend() {
const span = document.createElement('span');
span.innerHTML = "<h5 example.bind=\"message\"></h5>";
this._templatingEngine.enhance({ element: span, bindingContext: this, resources: this._viewResources });
this.view.appendChild(span);
}
}
app.html
<template>
<require from="./example-custom-attribute"></require>
<div ref="view"></div>
</template>
Gist.run:
https://gist.run/?id=7b80d2498ed17bcb88f17b17c6f73fb9
Additional resources
Dwayne Charrington has written an excellent tutorial on this topic:
https://ilikekillnerds.com/2016/01/enhancing-at-will-using-aurelias-templating-engine-enhance-api/

does an onclick function go in model, view or controller?

I am using backbone.js and trying to stay strict to the model-view-controller structure as I learn it. I have an onclick function for a link in one of my views that I am not sure where to put. Is the best place to keep this in the render function of the view?
Thanks
More specifically, the onclick performs a facebook login and then adds the user to my database if they are not currently in it. Don't know if this changes anything.
Here is what I think I will go with:
var NewUserView = Backbone.View.extend({
el: $('#window'),
render: function(){
// Render
this.listeners();
},
listeners: function(){
// onclick and other listeners
}
});
From the Backbone documentation:
In Backbone, the View class can also be thought of as a kind of
controller, dispatching events that originate from the UI, with the
HTML template serving as the true view. We call it a View because it
represents a logical chunk of UI, responsible for the contents of a
single DOM element.
Here's the general way to handle events in Backbone:
var NewUserView = Backbone.View.extend({
el: $('#window'),
render: function() {
// Render
},
events: {
"click #facebookButton": "loginViaFacebook"
},
loginViaFacebook: {
// Perform facebook login and add user to database
}
});
Where do you want the link to appear? On View page right? So , you should keep it in the same view on which you want the link to appear.
But , if you are building an architecture rather than just a web application, then you should put the onclick function in some different file where you will keep all these function and then import them in the view as required or keeping them in separate files and bundling them for import on view page.
Please make a file and write all the functions in that file and include that file in the your view file and use the onClick in the anchor tag. Please let me know if this make sense.

Backbone JS where to put Application main logic

Using Backbone i'm starting to build an App where i have everything cleanly separated. But now i have the following question. Where should i put the App main logic, in the views or in the model.
For example i have a view and a model, which are binded to a button and when i click that button i have to make
$.ajax(params)
do i put that in the view or the view calls a method with :
this.model.doAction(params)
which do you think is the best approach?
You can define an events property in the view which is of the format {"event selector": "callback"} for eg. {"click .collapse": "collapse"} where collapse would be a function defined as a property of the view. Then write your ajax request code in this callback function.
Also, unless I am missing something, "binding a view and model to a button" doesn't sound correct Backbone way to me. Instead you should think of one instance of model associated with one instance of the view. Whenever an attribute of the the model instance changes, a model change event will be triggered. You can bind a view function to this event so that change in the model is reflected in the view. Here is a quick example
var Book = Backbone.Model.extend({
// ...
});
var BookView = Backbone.View.extend({
initialize: function () {
this.model.bind('change', this.render, this);
},
render: function () {
// here, make changes to the dom as per changes in model
}
});
To associate a model with a view instance, you can pass the it while instantiating a new
view object..
var book = new Book({
title: "A great book"
});
var view = new BookView({model: book});
view.model.set('author', 'AGreatAuthor');
The set function call will fire change event and will result in render function of view
to be called.
Refer to the annotated source of Todos app example for a complete example.

ExtJS 4 how to create and display a new controller/view from another controller/view?

I have looked over lots of examples of ExtJS 4 MVC, and they all pretty much show the same thing: The application creates a viewport, loads in a view, and has a 'controllers' defined, which init's the controller:
Ext.application({
name: 'AM',
controllers: [
'Users'
],
launch: function() {
Ext.create('Ext.container.Viewport', {
layout: 'fit',
items: [
{
xtype: 'userlist'
}
]
});
}
});
Thats great, but now let's say in my application I want a button contained within my view to open a whole new controller/view, how do you do that?
I think what I am looking for is a way to say something like:
- Create Controller (run it's init code)
- in the controller init code, create the view and display it
Is that correct, and how do you do this?
I want to clarify that in my case I would need TWO individual instances of the SAME controller/view combination. For example, I might have a view with a tab panel and two tabs. I then want to put TWO separate instances of a 'Users' controller and 'user.List' view inside each tab.
I think what I am looking for is a way to say something like: - Create Controller (run it's init code) - in the controller init code, create the view and display it
In extjs, all controllers get instantiated when the application is loaded. You can use the launch method in the Application class to start off a view. And Have a controller listen to events of that view. In a controller, you can always access the other controller using the application object:
this.application.getController('ControllerName1').displayListPanel(options);
In the above code, I am calling a method displayListPanel that is available in ControllerName1 controller. This method holds the code to display a view (a grid panel) onto the screen. Similarly, I can have methods that create views like a new form for data entry. Here is another example:
this.application.getController('ControllerName1').newDateForm();
and In my method:
newDataForm : function() {
var view = Ext.widget('form',{title: 'New Data'});
view.show();
},
Just checked the documentation of new controller and view classes.
It seems to me, that you could always find needed view when you need it.
For example you can:
//somewhere in controller
this.getView('Viewport').create(); // or .show()
check this and view class methods:
http://docs.sencha.com/ext-js/4-0/#!/api/Ext.app.Controller-method-getView

How to change WebGrid action for getting data (.NET MVC3)

I have a Partial View that renders WebGrid. My controller looks like
public ActionResult Index()
{
return View();
}
public ActionResult GetUserList(int? page, string sort, string sortdir)
{
var model = UserModel.getList(page,sort,sortdir);
return PartialView("_UserList",model);
}
Index.cshtml :
....
#Html.Action("GetUserList")
The problem is that every time I click on grid navigation or sort links it calls Index method. How can I make Webgrid to execute a different action (GetUserList in this case)? I'm sure I can prepend GetUserList to all links in grid using jquery, but I believe it should be a better way.
It's also possible that what I'm doing is completely wrong, so thanks for your suggestions.
After lot of monkeying around and digging (and even fiddling with Reflector with WebGrid's source code), I came to the conclusion that with WebGrid, you cannot control/change the Header link action.
To create the header link URL, the path is taken from HttpContext.Request.Path, so there is no way to customize it to point to a different route.
One very ugly hack would be to tap into to jQuery Ajax's events (since the header link uses jQuery.load to sort) and overwrite the URL:
Album Id
Better solution would be to use:
Telerik Grid which lets you specify custom routes and also offers much more flexibility in rendering your layout
or MvcContrib Grid (not sure if this lets you modify header links but definitely offers more flexibility than WebGrid)
#MrChief had the idea above about the ugly hack...I put that together. Here is the main code that I used to do this. It does, indeed, hijack the ajax call before it is put on the wire. The key is to modify the URL that is getting sent because the grid will grab that URL from HttpContext.Request.Path. and plug it into the onclick for the anchor element.
I put this into my main common.js and will simply attach a function to capture the ajaxSend event which happens just before the data is sent.
// Used to hijack the sending of all AJAX calls. Before it sends the call to the server, it checks to see if the
// active element (the element that prompted the call) is marked with a given class. If so, then it will perform
// the given operation.
$(document).ajaxSend(function (event, jqXHR, ajaxOptions) {
var activeElement = document.activeElement;
if ($(activeElement).attr('redosorturl') != null) {
// If this is a sort anchor link from a grid that needs to have the sort link redone, do it here.
// the code is in the eipGrip.js file.
if ($(activeElement).attr('redosorturl').toString() == 'redoSortURL') {
var newURL = RedoGridSortURL(activeElement, ajaxOptions.url.toString());
ajaxOptions.url = newURL.toString();
}
}
return false;
});
When rendering the page, I have marked the tag in column header that contains the incorrect URL with a class named "redosorturl', so I know when I hijack the ajax call, the operation has to be done on this element. I then call a custom function that gives me the correct URL, then the ajaxOptions.url is then rewritten with that new URL.
I have to pass the activeElement to that rewrite function so I can traverse up the DOM to get the grid information, where I have put data like the controller and action method that is used along with and IDs and other info that I use for the URL. Likewise, I pass in the current url string because the grid will inject a token at the end of the url that I parse off and put on the new url.
Your conclusion isn't right. You just need to wrap your webgrid in a Get form:
using (Html.BeginForm("GetUserList", "ThingaMaBob", System.Web.Mvc.FormMethod.Get))
{
var grid = new WebGrid(
...
));
Html.Hidden(grid.SortFieldName, grid.SortColumn);
Html.Hidden(grid.SortDirectionFieldName, grid.SortDirection == SortDirection.Ascending ? "ASC" : "DESC");
}
The hiddens are so that the sort dir and sort field end up in parseable form in the querystring. You end up with urls like localhost/ThingaMaBob/GetUserList?someotherfields=whatever=&sort=city&sortdir=ASC
If you remove [HttpPost] attribute and let the route come to the same function. you'll find the Request["page"] value in your method. this will allow you to put a check on Request["Page"] value.

Resources