I'm trying to do something which must be really simple to accomplish in Ember.
I want to show a button in my template based on the boolean state of a property:
{{#if canFavoriteTag}}
{{d-button action="favoriteTag" label="tagging.favorite" icon="star-o" class="admin-tag favorite-tag"}}
{{else}}
{{d-button action="unFavoriteTag" label="tagging.unfavorite" icon="star-o" class="admin-tag favorite-tag tag-unfavorite"}}
{{/if}}
I have created a property called canFavoriteTag with a function which I want to return true or false to the template based on whether the user can favorite the tag or not:
export default Ember.Controller.extend(BulkTopicSelection, {
canFavoriteTag: function() {
const self = this;
var ticker = this.get('tag.id');
console.log('checking can fav stock:' + ticker);
Discourse.ajax("/stock/get_users_favorite_stocks", {
type: "GET",
}).then(function(data) {
var favable = true;
for (var i = data.stock.length - 1; i >= 0; i--) {
var stock = jQuery.parseJSON(data.stock[i]);
if(ticker.toLowerCase() == stock.symbol.toLowerCase()) { console.log(ticker + ' is a favorite stock: ' + stock.symbol.toLowerCase()); favable = false; }
}
console.log(favable);
return favable;
});
}.property('canFavoriteTag') <-- unsure about this?
...
When the page loads, the wrong button shows (always the "false" one).. I see in the console that the favable variable gets set to false when the ajax call completes, but the button never changes. How do I get it to show the right button based on the function? Do I need to use a promise? If so, how?
Related
I have four checkbox and I want to check automatically checkbox with id = 2 if checkbox with id = 4 is checked.
I did the following but did not get the output. Could someone help me with this.
{#each category in checkboxList}}
{{input id = category.CHECKBOX_ID type="checkbox" checked=category.IS_CHECKED}}
{{#if category.CHECKBOX_ID == 4 && category.IS_CHECKED == true}}
{{action 'CheckSize'}}
{{/if}}
The checkboxList is
[
{"IS_CHECKED":false,"CHECKBOX_ID":1},
{"IS_CHECKED":false,"CHECKBOX_ID":2},
{"IS_CHECKED":true,"CHECKBOX_ID":3},
{"IS_CHECKED":false,"CHECKBOX_ID":4}
]
You'll want to manage the state of the checkboxes separately.
Here is an example I did for another SO question that had a similar problem to solve:
https://ember-twiddle.com/468a737efbbf447966dd83ac734f62ad
The gist of it is
we use a single action in response to a click of any checkbox:
#action
toggleChecked(id) {
const newTree = check(this.options, id);
this.set('options', newTree);
}
In this example (taken from the ember-twiddle), all of the logic is extracted to a pure-function named check.
Check itself is pretty involved, but because the application logic is different between that example and the problem you've run in to, I'll just show the entry point function:
export function check(tree, id, transform = toggle) {
if (tree === undefined) return undefined;
if (Array.isArray(tree)) {
return tree.map(t => check(t, id, transform));
}
if (tree.id === id || id === 'all') {
return checkNode(tree, id, transform);
}
if (tree.children) {
return checkChildren(tree, id, transform);
}
return tree;
}
This is just an example of how you can immutably modify the representation of all checkboxes by using a pure function. Your logic may vary.
Hope this helps :)
I have a task form (OOB) where user needs to enter comments only when clicks on rejected button.
I wrote following script but this is firing even when clicked on approved button.
$('input[value=Rejected]').click(function() {
PreSaveAction();
});
});
function PreSaveAction()
{
var commentsBox = getFieldControl('ApprovalComments','Text');
var comments=$("textarea[title='ApprovalComments']").val();
if(comments.length==0)
{
var errorHtml='';
errorHtml = '<span class="ms-formvalidation"><span role="alert">Please enter Comments<br></span> </span>';
commentsBox.after(errorHtml);
return false;
}
return false;
}
function getFieldControl(fieldName,fieldType)
{
var control = $('[id^=' + fieldName + '_][id$=' + fieldType + 'Field]');
return control;
}
Thanks in advance
You can actually achieve it easily. Below is the jquery code sample:
function PreSaveAction()
{
var currentPage = decodeURIComponent(window.location.href.replace(/\+/g, " "));
if (currentPage.contains("Lists/WorkflowTasks/EditForm.aspx"))
{
var commentsTextArea=$("textarea[title='ApprovalComments']");
if(commentsTextArea.val().trim()=="")
{
var errorHtml='';
errorHtml = '<span class="ms-formvalidation"><span role="alert">Please enter Comments<br></span></span>';
commentsTextArea.after(errorHtml);
return false;
}
else
{
return true;
}
}else
{
return true;
}
}
String.prototype.contains = function (it) {
return this.indexOf(it) != -1;
};
Explanation:
PreSaveAction method will be triggered in all the New and Edit Forms in SP2010/SP2013.
Hence there is no need of Button onClick methods for client side validation on these forms.
In the above code, when you click of Save/Approved/Rejected buttons in your page, PreSaveAction will be fired.
We will be checking if current page is of Task Forms (WorkflowTasks is the name of the task List , please update this accordingly), else return true.
If the ApprovalComments is empty then error message will be shown and return false.
Else, return true.
Also notice the helper method String.prototype.contains.
** I'm using Ember.Object instead of Ember Data, to pull data from an api and I believe that might be causing the issue.**
I have my resources nested as so:
Mdm.Router.map ->
#resource "groups", ->
#resource "group", path: ':group_id'
'/groups/ loads a list of all groups on the left side of the browser. Each group is linking to its specific group_id. When clicked, the 'group' template renders on the right side of the screen, showing details of one group while the list remains to the left.
However, when you click the back button, or manually enter the group_id into the url the individual groups dont render. The url will update in the browser window, but the content wont change to match it.
I have the singular 'group' template rendering inside of the 'groups' template with the {{outlet}}.
My groups_route.js.coffee looks like this:
Mdm.GroupsRoute = Ember.Route.extend(model: ->
Mdm.Group.all()
)
application.hbs:
<div class="container">
<div class="nav-bar">
<img src="assets/logo_loginbox.png" class="logo">
<ul class="nav-menu">
<li>GROUPS</li>
<li>USERS</li>
</ul>
</div><!-- nav-bar -->
<hr>
{{outlet}}
</div><!-- container -->
groups.hbs:
<h1>Groups</h1>
{{ partial groupsList }}
<div class="group">
{{outlet}}
</div><!-- group -->
group.hbs:
<h1>{{name}}</h1>
I'm getting the following error in the console when I use the back button or try to load the page with the group_id present:
Uncaught TypeError: Object function () {
if (!wasApplied) {
Class.proto(); // prepare prototype...
}
o_defineProperty(this, GUID_KEY, undefinedDescriptor);
o_defineProperty(this, '_super', undefinedDescriptor);
var m = meta(this);
m.proto = this;
if (initMixins) {
// capture locally so we can clear the closed over variable
var mixins = initMixins;
initMixins = null;
this.reopen.apply(this, mixins);
}
if (initProperties) {
// capture locally so we can clear the closed over variable
var props = initProperties;
initProperties = null;
var concatenatedProperties = this.concatenatedProperties;
for (var i = 0, l = props.length; i < l; i++) {
var properties = props[i];
Ember.assert("Ember.Object.create no longer supports mixing in other definitions, use createWithMixins instead.", !(properties instanceof Ember.Mixin));
for (var keyName in properties) {
if (!properties.hasOwnProperty(keyName)) { continue; }
var value = properties[keyName],
IS_BINDING = Ember.IS_BINDING;
if (IS_BINDING.test(keyName)) {
var bindings = m.bindings;
if (!bindings) {
bindings = m.bindings = {};
} else if (!m.hasOwnProperty('bindings')) {
bindings = m.bindings = o_create(m.bindings);
}
bindings[keyName] = value;
}
var desc = m.descs[keyName];
Ember.assert("Ember.Object.create no longer supports defining computed properties.", !(value instanceof Ember.ComputedProperty));
Ember.assert("Ember.Object.create no longer supports defining methods that call _super.", !(typeof value === 'function' && value.toString().indexOf('._super') !== -1));
if (concatenatedProperties && indexOf(concatenatedProperties, keyName) >= 0) {
var baseValue = this[keyName];
if (baseValue) {
if ('function' === typeof baseValue.concat) {
value = baseValue.concat(value);
} else {
value = Ember.makeArray(baseValue).concat(value);
}
} else {
value = Ember.makeArray(value);
}
}
if (desc) {
desc.set(this, keyName, value);
} else {
if (typeof this.setUnknownProperty === 'function' && !(keyName in this)) {
this.setUnknownProperty(keyName, value);
} else if (MANDATORY_SETTER) {
Ember.defineProperty(this, keyName, null, value); // setup mandatory setter
} else {
this[keyName] = value;
}
}
}
}
}
finishPartial(this, m);
delete m.proto;
finishChains(this);
this.init.apply(this, arguments);
} has no method 'find' application.js:51233
Mdm.GroupRoute.Ember.Route.extend.model application.js:51233
superWrapper application.js:12849
Ember.Route.Ember.Object.extend.deserialize application.js:36503
collectObjects application.js:35614
proceed application.js:35638
(anonymous function) application.js:1193
fire application.js:1038
self.fireWith application.js:1149
(anonymous function) application.js:1200
fire application.js:1038
self.fireWith application.js:1149
done application.js:8075
script.onload.script.onreadystatechange
According to this question, it was possible to do something like this with Handlebars rc1:
{{#each links}}
<li>{{#index}} - {{url}}</li>
{{/each}}
{{#index}} would basically give you the iteration index, which is really useful when creating tables.
When I try this with Ember.js rc3, I get an unexpected token error. Does this not work anymore? Did it ever work? Is there another way to get the iteration index?
It looks like it was possible. Can't get it to work with HBS RC3. Probably, is deprecated.
Here's a "hand written" HBS helper.
This can help you gettin the index with {{index}} and side by side you can know if the iteration in on first or last object of the Array with {{first}} and {{last}} respectively.
Ember.Handlebars.registerHelper("foreach", function(path, options) {
var ctx;
var helperName = 'foreach';
if (arguments.length === 4) {
Ember.assert("If you pass more than one argument to the foreach helper, it must be in the form #foreach foo in bar", arguments[1] === "in");
var keywordName = arguments[0];
options = arguments[3];
path = arguments[2];
helperName += ' ' + keywordName + ' in ' + path;
if (path === '') {
path = "this";
}
options.hash.keyword = keywordName;
} else if (arguments.length === 1) {
options = path;
path = 'this';
} else {
helperName += ' ' + path;
}
options.hash.dataSourceBinding = path;
// Set up emptyView as a metamorph with no tag
//options.hash.emptyViewClass = Ember._MetamorphView;
// can't rely on this default behavior when use strict
ctx = this || window;
var len = options.contexts[0][path].length;
options.helperName = options.helperName || helperName;
options.contexts[0][path].map(function(item, index) {
item.index = index;
item.first = index === 0;
item.last = index === len - 1;
})
if (options.data.insideGroup && !options.hash.groupedRows && !options.hash.itemViewClass) {
new GroupedEach(ctx, path, options).render();
} else {
return Ember.Handlebars.helpers.collection.call(ctx, Ember.Handlebars.EachView, options);
}
});
and this can be tested like
{{#foreach array}}
{{log index first last}}
{{/foreach}}
i had the same problem recently i finish by writing a bound helper and passing them objects via Binding for example item here is an ember DS.Store object and content is a 'content' of the controller. hope it
Ember.Handlebars.registerBoundHelper 'isPair', (content, options)->
item = options.hash.item
content_name = options.hash.content || 'content'
if #get(content_name).indexOf(item) % 2 == 0 then 'is-pair' else 'is-unpair'
and in you view you call it
{{isPair content itemBinding='order'}}
i don't know if it is what you looking for but it might give you some ideas how to use it in your project.
btw. Ember overwrites #each helper that's why there it no #index i suppose
I am using jquery tool for tab Ui,
Now I want to keep tab selected on page reload. Is there any way to do that? below is my code
$(function() {
// setup ul.tabs to work as tabs for each div directly under div.panes
$("ul.tabs").tabs("div.panes > div");
});
Here is a simple implementation of storing the cookie and retrieving it:
function getCookie(c_name) {
var i, x, y, ARRcookies = document.cookie.split(";");
for (i = 0; i < ARRcookies.length; i++) {
x = ARRcookies[i].substr(0, ARRcookies[i].indexOf("="));
y = ARRcookies[i].substr(ARRcookies[i].indexOf("=") + 1);
x = x.replace(/^\s+|\s+$/g, "");
if (x == c_name) {
return unescape(y);
}
}
}
function setCookie(c_name, value, exdays) {
var exdate = new Date();
exdate.setDate(exdate.getDate() + exdays);
var c_value = escape(value) + ((exdays == null) ? "" : "; expires=" + exdate.toUTCString());
document.cookie = c_name + "=" + c_value;
}
Then, to save/retrieve cookie data with jQuery UI Tabs:
$(function() {
// retrieve cookie value on page load
var $tabs = $('ul.tabs').tabs();
$tabs.tabs('select', getCookie("selectedtab"));
// set cookie on tab select
$("ul.tabs").bind('tabsselect', function (event, ui) {
setCookie("selectedtab", ui.index + 1, 365);
});
});
Of course, you'll probably want to test if the cookie is set and return 0 or something so that getCookie doesn't return undefined.
On a side note, your selector of ul.tabs may be improved by specifying the tabs by id instead. If you truly have a collection of tabs on the page, you will need a better way of storing the cookie by name - something more specific for which tab collection has been selected/saved.
UPDATE
Ok, I fixed the ui.index usage, it now saves with a +1 increment to the tab index.
Here is a working example of this in action: http://jsbin.com/esukop/7/edit#preview
UPDATE for use with jQuery Tools
According the jQuery Tools API, it should work like this:
$(function() {
//instantiate tabs object
$("ul.tabs").tabs("div.panes > div");
// get handle to the api (must have been constructed before this call)
var api = $("ul.tabs").data("tabs");
// set cookie when tabs are clicked
api.onClick(function(e, index) {
setCookie("selectedtab", index + 1, 365);
});
// retrieve cookie value on page load
var selectedTab = getCookie("selectedtab");
if (selectedTab != "undefined") {
api.click( parseInt(selectedTab) ); // must parse string to int for api to work
}
});
function getCookie(c_name) {
var i, x, y, ARRcookies = document.cookie.split(";");
for (i = 0; i < ARRcookies.length; i++) {
x = ARRcookies[i].substr(0, ARRcookies[i].indexOf("="));
y = ARRcookies[i].substr(ARRcookies[i].indexOf("=") + 1);
x = x.replace(/^\s+|\s+$/g, "");
if (x == c_name) {
return unescape(y);
}
}
}
function setCookie(c_name, value, exdays) {
var exdate = new Date();
exdate.setDate(exdate.getDate() + exdays);
var c_value = escape(value) + ((exdays === null) ? "" : "; expires=" + exdate.toUTCString());
document.cookie = c_name + "=" + c_value;
}
Here is a working (unstyled) example: http://jsbin.com/ixamig/12/edit#preview
Here is what I see in Firefox when inspecting the cookie from the jsbin.com example:
This is what worked for me... at least I haven't run into any issues yet:
$('#tabs').tabs({
select: function (event, ui)
{
$.cookie('active_tab', ui.index, { path: '/' });
}
});
$('#tabs').tabs("option", "active", $.cookie('active_tab'));
I'm using: jQuery 1.8.2, jQuery UI 1.9.1, jQuery Cookie Plugin.
I set the "path" because in C# I set this value in a mvc controller which defaults to "/". If the path doesn't match, it wont overwrite the existing cookie. Here is my C# code to set the value of the same cookie used above:
Response.Cookies["active_tab"].Value = "myTabIndex";
Edit:
As of jQuery UI 1.10.2 (I just tried this version, not sure if it's broken in previous versions), my method doesnt work. This new code will set the cookie using jQuery UI 1.10.2
$('#tabs').tabs({
activate: function (event, ui) {
$.cookie('active_tab', ui.newTab.index(), { path: '/' });
}
});
The easiest way to survive between page refresh is to store the selected tab id in session or through any server-side script.
Only methods to store data on client side are: Cookies or localStorage.
Refer to thread: Store Javascript variable client side