Function call by expression in angularJS ng event - javascript

I have a problem where I want to map buttons created with ng-repeat to certain functions in the controller. I am defining a string with the function name like this:
$scope.sections = [
{
title: 'Music',
desc: 'Some examples of electronic music I have produced using Fl Studio.',
color: '#76a86f',
icon: 'img/icon/music.png',
onClick: 'clickMusic()'
}];
The function is defined like:
$scope.clickMusic = function() {
hideAll();
document.getElementById("musicContent").classList.remove("hide");
document.getElementById("musicContent").classList.add("show");
};
And then try to map the function to a html button like this:
<div ng-repeat="section in sections">
<div class="sectionThumbnail"">
<button ng-click="section.onClick">
<img ng-src="{{ section.icon }}" id="sectionnail_{{$index}}" ng-mouseover="sectionhoverIn($index)" ng-mouseleave="sectionhoverOut($index)">
</button>
<div class="sectionthumbnailInfo">
<p class="sectiontitle">{{ section.title }}</p>
{{ section.desc }}
</div>
</div>
</div>
However, when clicking the button, nothing happens. I know that the clickMusic() function works as intended once executed, since I use the same function for another button on the webpage. When directly referring to the clickMusic() function in the ng-click, like ng-click="clickMusic()", it also works. I get no error in the console either.
Is there any way to refer to functions from an array in this way, or do I have to create an additional function, which takes an index argument from the repeat and then map the index to the desired functions?
Thanks!

Change onClick: 'clickMusic()' to onClick: clickMusic (this way you store a reference to the function you want to call inside angular template) and then in ng-click simply call that funtion <button ng-click="section.onClick()">

Thanks to mic4ael's suggestion. I managed to figure it out.
in the controller:
$scope.sections = [
{
title: 'Music',
desc: 'Some examples of electronic music I have produced using Fl Studio.',
color: '#76a86f',
icon: 'img/icon/music.png',
onClick: $scope.clickMusic
}];
The $scope is there due to my function being defined as:
$scope.clickMusic = function() {
hideAll();
document.getElementById("musicContent").classList.remove("hide");
document.getElementById("musicContent").classList.add("show");
};
And then, just as mic4ael suggested, in HTML:
<button ng-click="section.onClick()">

Related

How "Data" can help me

I'm doing a web game with javaScript and KnockoutJs library.
In my html file I have a array foreach, and the number that this array saves, is the same number of buttons that a I have to draw on the page. Like this:
<strong data-bind = "foreach: cena1.opcoes">
<button data-bind="click: $parent.teste">Opcão</button>
<font color="red"><strong data-bind="text: conteudo"> </strong></font><br>
What I want to know is, how will I know which button the player selected?
I put data like a parameter on my button function, but I don't know how this works. like this:
object.teste = function(data) {
}
You can call the function this way:
<button data-bind="click: function() { $parent.teste($data); }">Opcão</button>
or
<button data-bind="click: function() { $parent.teste($data/* here can be any arguments available in the current binding context */); }">Opcão</button>
Update
By default the first parameter is being passed to the click handler function is the current view model - $data in the current binding context.
For more details and advanced scenarios you can check the Knockout JS documentation.
The click binding passes the current item to the bound function.
var vm = {
items: [1, 2, 3],
click: function(data) {
alert('You clicked: ' + data);
}
};
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="foreach:items">
<button data-bind="click:$parent.click">Click</button>
</div>

VueJs: How to Edit an Array Item

Simple Todo-App. Please excuse my ignorance for making a rather basic question.
But how would you go about and edit a certain item on an array?
Normally I would try to bind the value of my input to a new property on my data object and then assign this new property to the old property on click throuch Vue's two way databinding.
Like this: http://jsfiddle.net/z7960up7/
Well in my case I use the v-repeat directive, which loops through my data array but I can't use the v-model directive to use the two way databinding, because the values of the properties get corrupted if I do so. (See here: http://jsfiddle.net/doL46etq/2/)
And now I wonder, how I would go about updating my array of tasks:
My idea is to pass the VueObject (this) through my method on click, and then define the index on my event handler and then updating the tasks array, using the index, like this:
HTML:
<input v-el="editInputField" type="text" value="{{ task.body }}" v-on="keyup: doneEdit(this) | key 'enter'"/>
<button v-on="click: editTask(this)">
Edit
</button>
JS:
methods: {
editTask: function (task) {
var taskIndex = this.tasks.indexOf(task.task);
this.tasks[taskIndex] = {
'body': document.querySelector('input').value,
'completed': false
};
console.log(task.task.body);
},
}
Here is my fiddle about it:
http://jsfiddle.net/doL46etq/3/
But the data object is not updated at all and I wonder how I would go about it and update it.
What is the best way to edit an element on the array, using Vue?
Edit: An easy way, would just be to delete the element, and add the new to the array, using the push method, but I really want just to update the element, because I like to keep the dataobject in sync with my backend.
The short answer: Use a component in an extended constructor, then pass the index to that component in HTML as property and use computed properties to link back and forth to your data.
But don't be satisfied with just the short answer. Here is the long one:
Situation: I am using your JSFiddle as base for this answer.
in HTML you have:
<td>{{ task.body }}</td>
<td>
<div>
<input v-el="editInputField" type="text" value="{{ task.body }}" v-on="keyup: doneEdit(this) | key 'enter'" v-model="newEdit"/>
</div>
</td>
<td>
<button v-on="click: editTask(this)" class="mdl-button mdl-js-button mdl-button--icon"> <i class="material-icons">create</i>
</button>
</td>
We want to replace this code with the component. Using this component allows us to identify the index/row we are working on in your set of data.
<td v-component="listitem" index="{{$index}}"></td>
Next step: defining the component.
In order not to cloud our instance with the component, we will create a separate constructor for the vue object, so we can assign the new element to our new object.
This is done using extend.
window.newVue = Vue.extend({
components:
{
'listitem': {
props: ['index'],
computed: {
// both get and set
body: {
get: function () {
return this.$parent.tasks[this.index].body;
},
set: function (v) {
this.$parent.tasks[this.index].body = v
}
}
},
template: '<td>{{ body }}</td><td><div><input type="text" v-model="body" value="{{ body }}"/></div></td><td></td>',
}
}
});
Since we can't define our data properly using an extend, we'll just assume the data already exists while writing the component.
Step 3: defining the actual data:
Instead of using Vue as our object constructor, we'll now use our newly created instantiator.
new newVue({
el: '#todoapp',
data: {
tasks: [{
'body': 'Eat breakfast',
'completed': false
}, {
'body': 'Drink milk',
'completed': false
}, {
'body': 'Go to the store',
'completed': false
}],
newTask: '',
},
});
That's it!
In case you couldn't follow what happened: Here's the Fiddle!
PS: More information about the working of these code can be found on vuejs.org
Actually the simplest way to update an array item, is to two-way bind the task body with the v-model directive.
Example:
http://jsfiddle.net/z7960up7/2/
<div id="demo">
{{ message }}
<div class="edit">
<input type="text" v-model="message">
<button v-on="click: editMessage">Edit</button>
</div>
<pre>{{ $data | json }}</pre>
</div>
And fire an event whenever you blur out of the input box or the edit button is hit.
Also hide the input field with css, by using the v-class directive.

Change scope value in a different view

Ok, I have struggeled a lot with this thing for some days now and I'm stuck. Here is what I want; I want to change my scope value in a different view by clicking on a button. So if I'm in the index.html view and click on a button I want to change the value of a scope on the index2.html view, and then display that view. Here is an example of my code which is not working
index.html
<div ng-controller="IndexController">
<button class="button button-block button-assertive" ng-click="checkValues()" value="checkitems" >
check values
</button>
</div>
IndexController.js
angular
.module('legeApp')
.controller('IndexController', function($scope, supersonic, $filter) {
$scope.checkValues = function(){
$scope.Diagnose = 'test';
var view = new supersonic.ui.View("legeApp#index2.html");
var customAnimation = supersonic.ui.animate("flipHorizontalFromLeft");
supersonic.ui.layers.push(view, { animation: customAnimation });
};
});
index2.html
<div ng-controller="IndexController">
<div class="card">
<div class="item item-text-wrap">
Test<b>{{Diagnose}} </b>
</div>
</div>
</div>
I can give the value outside the checkValues method, but I want it to be two different values depending on the button you click. Please help
I tried the code suggested, but I received an error. What am I doing wrong?
I try the code below and receive this error "SyntaxError: Unexpected token ':' - undefined:undefined". I also did not quite understand how and why I want to target the new value with supersonic.ui.views.params.current in the new view. I want to get the new value in the new view, not in a controller?Do I need two different controllers? I just want to update my values in a html view without being in it.
supersonic.ui.layers.push:
( view: view,
options: { params: {$scope.Diagnose : 'test'}
animation: customAnimation
}) => Promise
According to the supersonic push docs, the params attribute is meant for passing parameters between views:
JSON string of optional parameters to be passed to the target View,
accessible via supersonic.ui.views.params.current.
Try calling
supersonic.ui.layers.push: (
view: view,
options: {
params: {valueToBeSentAccrossView: <Your Value>}
animation: customAnimation
}) => Promise
and then retrieving the value in the target view using supersonic.ui.views.params.current.

I have two divs with the same ng-controller in AngularJS, how can I make them share information?

I have two different div tags in my html code referencing the same controller in AngularJS. What I suspect is that since these divs aren't nested they each have their own instance of the controller, thus the data is different in both.
<div ng-controller="AlertCtrl">
<ul>
<li ng-repeat="alert in alerts">
<div class="span4">{{alert.msg}}</div>
</li>
</ul>
</div>
<div ng-controller="AlertCtrl">
<form ng-submit="addAlert()">
<button type="submit" class="btn">Add Alert</button>
</form>
</div>
I know this could easily be fixed by including the button in the first div but I feel this is a really clean and simple example to convey what I am trying to achieve. If we were to push the button and add another object to our alerts array the change will not be reflected in the first div.
function AlertCtrl($scope) {
$scope.alerts = [{
type: 'error',
msg: 'Oh snap! Change a few things up and try submitting again.'
}, {
type: 'success',
msg: 'Well done! You successfully read this important alert message.'
}];
$scope.addAlert = function() {
$scope.alerts.push({
type: 'sucess',
msg: "Another alert!"
});
};
}
This is a very common question. Seems that the best way is to create a service/value and share between then.
mod.service('yourService', function() {
this.sharedInfo= 'default value';
});
function AlertCtrl($scope, yourService) {
$scope.changeSomething = function() {
yourService.sharedInfo = 'another value from one of the controllers';
}
$scope.getValue = function() {
return yourService.sharedInfo;
}
}
<div ng-controller="AlertCtrl">{{getValue()}}</div>
<div ng-controller="AlertCtrl">{{getValue()}}</div>
If I understand the question correctly, you want to sync two html areas with the same controller, keeping data synced.
since these divs aren't nested they each have their own instance of the controller, thus the data is different in both
This isn't true, if you declare the controllers with the same alias (I'm using more recente angular version):
<div ng-controller="AlertCtrl as instance">
{{instance.someVar}}
</div>
<div ng-controller="AlertCtrl as instance">
{{instance.someVar}} (this will be the same as above)
</div>
However, if you WANT them to be different and comunicate each other, you will have to declare different aliases:
<div ng-controller="AlertCtrl as instance1">
{{instance1.someVar}}
</div>
<div ng-controller="AlertCtrl as instance2">
{{instance2.someVar}} (this will not necessarily be the same as above)
</div>
Then you can use services or broadcasts to comunicate between them (the second should be avoided, tough).

How to bind click handlers to templates in knockoutjs without having a global viewModel?

I'm very new to KnockoutJs so I'm hoping that there is a well known best practice for this kind of situation that I just haven't been able to find.
I have a view model that contains an array of items. I want to display these items using a template. I also want each item to to be able to toggle between view and edit modes in place. I think what fits best with Knockout is to create the relevant function on either the main view model or (probably better) on each item in the array and then bind this function in the template. So I have created this code on my page:
<ul data-bind="template: {name: testTemplate, foreach: items}"></ul>
<script id="testTemplate" type="text/x-jquery-tmpl">
<li>
<img src="icon.png" data-bind="click: displayEditView" />
<span data-bind="text: GBPAmount"></span>
<input type="text" data-bind="value: GBPAmount" />
</li>
</script>
<script>
(function() {
var viewModel = new TestViewModel(myItems);
ko.applyBindings(viewModel);
})();
</script>
And this in a separate file:
function TestViewModel(itemsJson) {
this.items = ko.mapping.fromJS(itemsJson);
for(i = 0; i < this.items.length; ++i) {
this.items[i].displayEditView = function () {
alert("payment function called");
}
}
this.displayEditView = function () {
alert("viewmodel function called");
}
};
Due to the environment my JS is running in I can't add anything to the global namespace, hence the annonymous function to create and set up the view model. (There is a namespace that I can add things to if it is necessary.) This restriction seems to break all the examples I've found, which seem to rely on a global viewModel variable.
P.S. If there's an approach that fits better with knockoutJS than what I am trying to do please feel free to suggest it!
When your viewModel is not accessible globally, there are a couple of options.
First, you can pass any relevant methods using the templateOptions parameter to the template binding.
It would look like (also note that a static template name should be in quotes):
data-bind="template: {name: 'testTemplate', foreach: items, templateOptions: { vmMethod: methodFromMainViewModel } }"
Then, inside of the template vmMethod would be available as $item.vmMethod. If you are using templateOptions as the last parameter, then make sure that there is a space between your braces { { or jQuery templates tries to parse it as its own.
So, you can bind to it like:
<img src="icon.png" data-bind="click: $item.vmMethod" />
The other option is to put a method and a reference to anything relevant from the view model on each item. It looks like you were exploring that option.
Finally, in KO 1.3 (hopefully out in September and in beta soon) there will be a nice way to use something like jQuery's live/delegate functionality and connect it with your viewModel (like in this sample: http://jsfiddle.net/rniemeyer/5wAYY/)
Also, the "Avoiding anonymous functions in event bindings" section of this post might be helpful to you as well. If you are looking for a sample of editing in place using a dynamically chosen template, then this post might help.
This is for those asking how to pass variable methods (functions) to Knockout Template. One of the core features of Templating is the consuming of variable data, which can be String or function. In KO these variables can be embedded in data or foreach properties for the Template to render. Objects embedded in data or foreach, be it String, function etc, can be accessed at this context using $data.
You can look at this code and see if it can help you to pass functions to Knockout Template.
function ViewModel() {
this.employees = [
{ fullName: 'Franklin Obi', url: 'employee_Franklin_Obi', action: methodOne },
{ fullName: 'John Amadi', url: 'employee_John_Amadi', action: methodTwo }
],
this.methodOne = function(){ alert('I can see you'); },
this.methodTwo = function(){ alert('I can touch you'); }
}
ko.applyBindings(new ViewModel());
<ul data-bind="template: { name: employeeTemplate, foreach: employees }" ></ul>
<script type="text/html" id="employeeTemplate">
<li><a data-bind="attr: { href: '#/'+url }, text: fullName, click: $data.action"></a></li>
</script>
If you want to serve multiple Template constructs you can introduce a switch method to your ViewModel like this, and use as property to introduce alias for each item (employee). Make sure you add the switch key, linkable, to the item object.
...
this.employees = [
{ fullName: 'Franklin Obi', linkable : false },
{ fullName: 'John Amadi', url: 'employee_John_Amadi', action: methodTwo, linkable : true }
],
this.methodLinkTemplate = function(employee){return employee.linkable ? "link" : "noLink"; } //this is a two way switch, many way switch is applicable.
...
Then the id of the Template forms will be named thus;
<ul data-bind="template: { name: employeeTemplate, foreach: employees, as: 'employee' }" ></ul>
<script type="text/html" id="noLink">
<li data-bind="text: fullName"></li>
</script>
<script type="text/html" id="link">
<li><a data-bind="attr: { href: '#/'+url }, text: fullName, click: $data.action"></a></li>
</script>
I have not ran this codes but I believe the idea can save someones time.

Categories