How can i manipulate dom in controller in emberjs? - javascript

Here is a scenario:
I load data when controller is init. And when loading is finished. I want to resize the container element's size according the load data. So here is the problem, how can i access the view in a controller?
I know i can manipulate dom in view by this.$() but how can i access dom in controller or how can i access view in controller. I use Ember.Router here. So i dont create view and controller manually.
http://jsbin.com/oxudor/edit#javascript,html I show some code sample here. The code can not be executed, but it can show my problem. I did some comments on the code where have the problem.

I think you should not play with dom in the controller. That breaks MVC. Perhaps one solution could be to define an observer in the view, listening to the content of the controller. In this observer, then play with the dom (you're in the view, so no problem).
As #pangratz suggests, with some code, perhaps I can give you a more complete answer.
EDIT: I think you could put the carousel function in the related view, and observes the controller.loading property.
Here you could retrieve the jquery object of the view by using this.$().
carousel: function() {
var context = this.get('controller'),
self = this;
if(context.get('loading') === false) {
setTimeout(function() {
var width = self.$('#page_wrapper').width(),
num = self.$('.page').size();
self.$('#pages').width(width * num);
self.$('.page').width(width);
console.log([width, num]);
console.log(context.get('elements'));
}, 100);
}
}.observes('controller.loading')
Hope this help...

Now you can use Modifiers, Install Ember-midifier library and see this tutorial Ember-Modifiers

Related

How to modify results from a read-only ng-app?

I apologize if this is a duplicate, just haven't been able to find anything close to this myself.
The company I work for has an online reporting system that is run by an ng-app applied directly to the body tag. I have been tasked with modifying the result that returns from this ng-app. Following code is called using onload attached to the body tag.
function getElements(){
var list;
list = document.getElementsByClassName("neutral");
[].forEach.call(list, function (listItem) {
addNeutral(listItem);
});
...
Basically, trying to find anything with class "neutral" and apply results from another function to it. The addNeutral function is basically just
element.classList.add("neutralHighlight");
This code seems to run and gathers the correct list of elements, but the new class is never added and no errors occur. So long story short, is there any way to modify the output of a ng-app using code separate from the ng-app itself? Any guidance would be greatly appreciated.
Update 3/5/20
So I implemented Shaun's response and it still isn't working properly. With some debug messages, I can see that it collects the "list" variable as an HTMLCollection. The forEach function doesn't seem to even trig
function getElements(){
var list;
list = document.getElementsByClassName("neutral");
console.log(list); //Debug - Shows in console
[].forEach.call(list, function (listItem) {
console.log(listItem); //Debug - Does not show in console
addNeutral(listItem);
});
}
function addNeutral(element){
angular.element(element).addClass("neutralHighlight");
console.log("!!!end addNeutral"); //Debug - Does not show in console
}
Update 3/9/20 -SOLUTION-
Application is returning the HTML Collection, but it displays with a length of 0 (still displays the objects, but I think that's a Firefox console thing). When trying to loop through the list items, it returns null for the first item, so the function is still being called before the Angular app loads completely.
That being said, I messed around with things a bit this morning and came to a solution! I ended up using the setInterval function with a 5 second interval (since I need it to update, I may change this to optimize it later by adding onChange items to the objects I grab initially). The setTimeout that was proposed would have worked with a delay added to it. This probably isn't the most elegant solution, and there's probably a better way to do it, but this works for anyone interested.
function getElements(){
var list;
list = document.getElementsByClassName("neutral");
for (i = 0; i <= list.length; i++){
var listItem = list.item(i);
addNeutral(listItem);
}
}
function loadFunction(){
setInterval(function(){getElements()}, 5000);
}
I added <script>loadFunction()</script> right before the closing HTML tag to execute.
Update 4/21/20 -IMPROVED SOLUTION- CSS Attributes
So this is a bit specific to my scenario, but I wanted to include it for anybody else who may come across this in the future. I was actually able to accomplish this entirely with CSS attribute selectors. The tags that I wanted to edit all had specific titles assigned to them via the ng-app provided from the company, so I was able to use CSS selectors like div [title~="NotReadyForNextCall"]{<custom styling here>} to select any block that included an agent who was not ready for their next call. This is a much better solution in terms of resources required to operate and overall performance, so I hope it helps anybody looking at this down the line!
You might be able to get around this by using the angular object in your code and adding the class on an angular.element instead. AngularJS doesn't use a virtual DOM but it does use its own node references (which is what makes it so tricky to work with outside of the framework, as Lex pointed out in the comments of your question). Try:
angular.element(element).addClass("neutralHighlight");
Yes, you have access to angular outside of the app! And a last note, addClass() is available on angular.element because AngularJS comes with jqLite.
Further investigation
It looks like the above solution works if the class 'neutral' is being added in angular via the class attribute, but it looks like your app may be adding it programmatically with the ng-class directive after the DOM has rendered.
I wrapped your getElements() function in a setTimeout():
setTimeout(getElements);
This is unfortunately not a guarantee that the ng-class update will have taken place, but what it does is it executes the function after the previous digest cycle has completed and that appears to be working.
An even safer solution would be to use document.ready but again with the angular.element wrapper. This will ensure the initial DOM state has been rendered by AngularJS, including applied directives:
angular.element(document).ready(function() {
getElements();
});
EDIT: Update 3/9/20 -SOLUTION-
The solution proposed in the answer is almost identical to the setTimeout() answer given here. The only difference is setInterval() will keep executing the code every 5 seconds until you tell it to stop.
You can do this with the following:
var loadFunction = setInterval(function() {
var el = getElements();
if (el) clearInterval(loadFunction);
}, 5000);
And just return a bool in your getElements() like so:
function getElements() {
var list;
var found = false;
list = document.getElementsByClassName("neutral");
[].forEach.call(list, function (listItem) {
addNeutral(listItem);
found = true;
});
return found;
}
See: codepen.io/shaunetobias/pen/KKpXRxq

sapui5 Block View Rendering until model is loaded

I want to load a Model in my onInit Method, before the onBeforeRendering Method is called.
The problem withe attachRequestCompleted is that this called sometime after Rendering.
For example i get this error withe the ProcessFlow:
Render must not be called within Before or After Rendering Phase. Call ignored. - [object Object]
So my question is: Give it a function that block the view until the model ist loading?
I instantiated my Views over a manifes.json and my Models in the Component.js. So show Code is a bit difficult, but i load my Model like this:
var oModel = new JSONModel().attachRequestCompleted(function(){...});
var oConfigModel = new JSON().attachRequestCompleted(function(){
oModel.loadData(oConfigModel.getURL());
});
oConfigModel.loadData("config.json");
I do this because i formating and make some Models in dependency of my Main Model.
And Currently i put the data in the ProcessFlow over Databinding in the xml.
Blocking the UI never is a good idea! Esp. doing synchronous request as suggested in comments is aweful. Syncronous request are even deprecated on the main thread in major browsers.
You could set your view busy or even invisible until your model data is loaded like this:
onInit: function() {
var oView = this.getView();
oView.setBusy(true);
// option 2 set invisible: oView.setVisible(false);
... insert model init here ...
var oModel = ...
oModel.attachEventOnce("requestCompleted", function() {
oView.setBusy(false);
// option 2 set visible: oView.setVisible(true);
});
}
Note the use of attachEventOnce instead of attachRequestCompleted that will only execute - guess what - once.
Btw: Why is it so important to block or not show the UI at all? It is a much better experience for a user to already see sth. eventhough a view might be empty initially.
BR
Chris
An option here could be to use a busy indicator.
Start the indicator in your init function:
sap.ui.core.BusyIndicator.show();
...and stop the indicator in your attachRequestCompleted callback function:
sap.ui.core.BusyIndicator.hide();
More information here.

How to re-evaluate a script that doesn't expose any global in a declarative-style component

I have been writing a reusable script, let's call it a plugin although it's not jQuery, that can be initialised in a declarative way from the HTML. I have extremely simplified it to explain my question so let's say that if a user inserts a tag like:
<span data-color="green"></span>
the script will fire because the attribute data-color is found, changing the color accordingly.
This approach proved very handy because it avoids anyone using the plugin having to initialise it imperatively in their own scripts with something like:
var elem = document.getElementsByTagName('span')[0];
myPlugin.init(elem);
Moreover by going the declarative way I could get away without defining any global (in this case myPlugin), which seemed to be a nice side effect.
I simplified this situation in an example fiddle here, and as you can see a user can avoid writing any js, leaving the configuration to the HTML.
Current situation
The plugin is wrapped in a closure like so:
;(function(){
var changeColor = {
init : function(elem){
var bg = elem.getAttribute('data-color');
elem.style.background = bg;
}
};
// the plugin itslef looks for appropriate HTML elements
var elem = document.querySelectorAll('[data-color]')[0];
// it inits itself as soon as it is evaluated at page load
changeColor.init(elem);
})();
The page loads and the span gets the correct colour, so everything is fine.
The problem
What has come up lately, though, is the need to let the user re-evaluate/re-init the plugin when he needs to.
Let's say that in the first example the HTML is changed dynamically after the page is loaded, becoming:
<span data-color="purple"></span>
With the first fiddle there's no way to re-init the plugin, so I am now testing some solutions.
Possible solutions
Exposing a global
The most obvious is exposing a global. If we go this route the fiddle becomes
http://jsfiddle.net/gleezer/089om9z5/4/
where the only real difference is removing the selection of the element, leaving it to the user:
// we remove this line
// var elem = document.querySelectorAll('[data-color]')[0];
and adding something like (again, i am simplifying for the sake of the question):
window.changeColor = changeColor;
to the above code in order to expose the init method to be called from anywhere.
Although this works I am not satisfied with it. I am really looking for an alternative solution, as I don't want to lose the ease of use of the original approach and I don't want to force anyone using the script adding a new global to their projects.
Events
One solution I have found is leveraging events. By putting something like this in the plugin body:
elem.addEventListener('init', function() {
changeColor.init(elem);
}, false);
anybody will be able to just create an event an fire it accordingly. An example in this case:
var event = new CustomEvent('init', {});
span.dispatchEvent(event);
This would re-init the plugin whenever needed. A working fiddle is to be found here:
http://jsfiddle.net/gleezer/tgztjdzL/1/
The question (finally)
My question is: is there a cleaner/better way of handling this?
How can i let people using this plugin without the need of a global or having to initialise the script themselves the first time? Is event the best way or am I missing some more obvious/better solutions?
You can override Element.setAttribute to trigger your plugin:
var oldSetAttribute = Element.prototype.setAttribute;
Element.prototype.setAttribute = function(name, value) {
oldSetAttribute.call(this, name, value);
if (name === 'data-color') {
changeColor.init(this);
}
}
Pros:
User does not have to explicitly re-initialize the plugin. It will happen automatically when required.
Cons:
This will, of course, only work if the user changes data-color attributes using setAttribute, and not if they create new DOM elements using innerHTML or via some other approach.
Modifying host object prototypes is considered bad practice by many, and for good reasons. Use at your own risk.

is this a bad / inelegant way to handle this? Would backbone or ember help me?

I have a lot of the following types of html / jquery and am wondering if there is a better way to handle it. I am trying to separate the binding of html, instantiation of object, a processing (post and calling view rendering), and a view layer that deletes out the container with the id.
html:
<span data-global-id="1" class="remove-item">remove</span>
event capture.js:
$('.remove-item').click(function(){
var l=get_remove_item($(this));
process(l); // this does the posting based upoint values in objectction
});
object-creation.js:
function get_remove_item(v){
t={};
t.action='remove-item';
t.global_id=v.data('global-id');
console.log(t);
return t;
}
process.js:
function process(l){
// will post the data
var f_name = s.replace (/-/g,"_"); // replace space or comma by underscore
f_name=f_name + "_view";
var str=window[f_name](r);
}
view.js:
remove_item_view(){
// go back and delete out container with global_id value
}
Is there a better way to do this? Would backbone or ember handle this better? In what way?
thx
Backbone would help organize the app and make updating the view easier. Ember would make things even simpler than Backbone. Because of Ember's binding, you wouldn't have remove the element from the DOM. You'd just delete the model and Ember would update the view for you.

templates vs DOM creation - highly dynamic interface

Building a browsergame I came from PHP to JavaScript, which I now also want to use at the server side.
As I'm going to require Users to have JavaScript either way, I'm going to take extensive use of it. I want to use in in a object-oriented way though.
Considering MVC, Models will be used on both client and server side. Views are only used at the client side.
The interface is split into multiple parts: a main sidemenu, main content and some widgets. I'll take the part I've already done as example:
The menu is split into three categories with multiple entries. Each entry is a link with an attached action (like switching the content).
// menuview:
var self = new View();
var generalMenu = new MenuCategory('generalmenu')
.addEntry(new MenuEntry('overview', new Action()))
.addEntry(new MenuEntry('buildings'))
.addEntry(new MenuEntry('resources'))
// [..more categories..]
self.toData = function() {
return {
id: this.id,
cat: [generalMenu.toData(), infosMenu.toData(), userMenu.toData()]
};
};
At the moment View is a compositum with a toData() method to create data for the template parser(selfmade, simple but supporting iteration). And the actions get attached after creation. I use jQuery as framework:
self.show = function(callback) {
$tpl(this.tpl).parse(this.toData()).lang('main').toHTML(function(html) {
var el = $(html);
el.find('a').click(function (e) {
MenuEntry.actionHandler.execAction(e.target.id);
return false;
});
el.appendTo('#'+self.target);
callback && callback();
});
return this;
};
I have declared an actionhandler to avoid iterating over the links.
I'm not feeling well with this solution, it's not flexible enough. I'd like to treat a view like a real compositum, not with a lot of strange dependencies. Also, I have to reparse the whole View if I change a part. Well, in this example this is not obvious, because the menu wont change while runningtime, but other parts of the interface will.
Now, to finally get to my question: Is there a better solution?
Like having dom references spread over the view, each menuentry having it's own reference and directly attached action? If I'm not using templates anymore, what kind of flexiblity am I losing?
I decided to go without template parser. Each view stores it's node and is able to manipulate it directly if it gets informed to update the data.

Categories