Refactor selector components for extensibility

This commit is contained in:
Robin Ward 2014-12-17 15:09:06 -05:00
parent a026a7c5ec
commit a8acbc37a2
15 changed files with 118 additions and 182 deletions

View file

@ -1,29 +1,13 @@
var compiled;
function templateFunction() {
compiled = compiled || Handlebars.compile(
"<div class='autocomplete'>" +
"<ul>" +
"{{#each options}}" +
"<li>" +
"<a href=''>{{this.name}}</a>" +
"</li>" +
"{{/each}}" +
"</ul>" +
"</div>"
);
return compiled;
}
export default Em.Component.extend({
export default Ember.Component.extend({
placeholder: function(){
return I18n.t(this.get("placeholderKey"));
}.property("placeholderKey"),
didInsertElement: function() {
_initializeAutocomplete: function() {
var self = this;
var selectedGroups;
var template = this.container.lookup('template:group-selector-autocomplete.raw');
self.$('input').autocomplete({
allowAny: false,
onChangeItems: function(items){
@ -45,7 +29,7 @@ export default Em.Component.extend({
});
});
},
template: templateFunction()
template: template
});
}
}.on('didInsertElement')
});

View file

@ -0,0 +1,62 @@
import TextField from 'discourse/components/text-field';
import userSearch from 'discourse/lib/user-search';
export default TextField.extend({
_initializeAutocomplete: function() {
var self = this,
selected = [],
currentUser = this.currentUser,
includeGroups = this.get('includeGroups') === 'true';
function excludedUsernames() {
if (currentUser && self.get('excludeCurrentUser')) {
return selected.concat([currentUser.get('username')]);
}
return selected;
}
var template = this.container.lookup('template:user-selector-autocomplete.raw');
$(this.get('element')).val(this.get('usernames')).autocomplete({
template: template,
disabled: this.get('disabled'),
single: this.get('single'),
allowAny: this.get('allowAny'),
dataSource: function(term) {
return userSearch({
term: term,
topicId: self.get('topicId'),
exclude: excludedUsernames(),
includeGroups: includeGroups
});
},
transformComplete: function(v) {
if (v.username) {
return v.username;
} else {
var excludes = excludedUsernames();
return v.usernames.filter(function(item){
return excludes.indexOf(item) === -1;
});
}
},
onChangeItems: function(items) {
items = items.map(function(i) {
return i.username ? i.username : i;
});
self.set('usernames', items.join(","));
selected = items;
},
reverseTransform: function(i) {
return { username: i };
}
});
}.on('didInsertElement')
});

View file

@ -0,0 +1,10 @@
import registerUnbound from 'discourse/helpers/register-unbound';
registerUnbound('max-usernames', function(usernames, params) {
var maxLength = parseInt(params.max) || 3;
if (usernames.length > maxLength){
return usernames.slice(0, maxLength).join(", ") + ", +" + (usernames.length - maxLength);
} else {
return usernames.join(", ");
}
});

View file

@ -1,43 +0,0 @@
var deprecatedViewHelpers = {
inputTip: 'input-tip',
pagedown: 'pagedown-editor',
textField: 'text-field',
userSelector: 'user-selector',
combobox: 'combo-box',
categoryChooser: 'category-chooser',
chooseTopic: 'choose-topic',
'discourse-activity-filter': 'activity-filter'
};
var renamedHelpers = {
icon: "fa-icon",
date: "format-date",
age: "format-age"
};
export default {
name: 'deprecations',
initialize: function(container) {
Ember.keys(deprecatedViewHelpers).forEach(function(old) {
var newName = deprecatedViewHelpers[old];
Ember.Handlebars.registerHelper(old, function(options) {
Em.warn("The `" + old +"` helper is deprecated. Use `" + newName + "` instead.");
var helper = container.lookupFactory('view:' + newName) || container.lookupFactory('component:' + newName);
var hash = options.hash,
types = options.hashTypes;
Discourse.Utilities.normalizeHash(hash, types);
return Ember.Handlebars.helpers.view.call(this, helper, options);
});
});
Ember.keys(renamedHelpers).forEach(function(old) {
var newName = renamedHelpers[old];
Ember.Handlebars.registerHelper(old, function() {
Em.warn("The `" + old +"` helper is deprecated. Use `" + newName + "` instead.");
var newHelper = container.lookupFactory('helper:' + newName);
return newHelper.apply(this, Array.prototype.slice.call(arguments));
});
});
}
};

View file

@ -39,5 +39,9 @@ export default {
application.inject('route', 'session', 'session:main');
application.inject('view', 'session', 'session:main');
application.inject('model', 'session', 'session:main');
// Inject currentUser. Components only for now to prevent any breakage
application.register('current-user:main', Discourse.User.current(), { instantiate: false });
application.inject('component', 'currentUser', 'current-user:main');
}
};

View file

@ -1,6 +1,5 @@
var helpers = ['input-tip',
'pagedown-editor',
'user-selector',
'category-chooser',
'combo-box',
'choose-topic',

View file

@ -74,7 +74,7 @@ function organizeResults(r, options) {
export default function userSearch(options) {
var term = options.term || "",
includeGroups = !!options.include_groups,
includeGroups = options.includeGroups,
topicId = options.topicId;

View file

@ -8,7 +8,7 @@
{{render "composer-messages"}}
<div class='control'>
<a href='#' class='toggler' {{action "toggle" bubbles=false}} title='{{i18n 'composer.toggler'}}'></a>
<a href class='toggler' {{action "toggle" bubbles=false}} title='{{i18n 'composer.toggler'}}'></a>
{{#if model.viewOpen}}
<div class='control-row reply-area'>
@ -33,7 +33,7 @@
{{user-selector topicId=controller.controllers.topic.model.id
excludeCurrentUser="true"
id="private-message-users"
include_groups="true"
includeGroups="true"
class="span8"
placeholderKey="composer.users_placeholder"
tabindex="1"

View file

@ -0,0 +1,7 @@
<div class='autocomplete'>
<ul>
{{#each options}}
<li><a href>{{this.name}}</a></li>
{{/each}}
</ul>
</div>

View file

@ -6,7 +6,7 @@
<form>
<label>{{i18n 'topic.change_owner.label'}}</label>
{{user-selector single=true usernames=new_user include_groups="false" placeholderKey="topic.change_owner.placeholder"}}
{{user-selector single="true" usernames=new_user placeholderKey="topic.change_owner.placeholder"}}
</form>
</div>

View file

@ -15,7 +15,7 @@
{{#if isAdmin}}
<label>{{{groupInstructions}}}</label>
{{group-selector includeAuto=false groupFinder=groupFinder groupNames=groupNames placeholderKey="topic.invite_private.group_name"}}
{{group-selector groupFinder=groupFinder groupNames=groupNames placeholderKey="topic.invite_private.group_name"}}
{{/if}}
{{/if}}
</div>

View file

@ -10,7 +10,7 @@
{{i18n 'topic.invite_private.success'}}
{{else}}
<label>{{i18n 'topic.invite_private.email_or_username'}}</label>
{{user-selector single=true allowAny=true usernames=emailOrUsername include_groups="true" placeholderKey="topic.invite_private.email_or_username_placeholder"}}
{{user-selector single="true" allowAny=true usernames=emailOrUsername includeGroups="true" placeholderKey="topic.invite_private.email_or_username_placeholder"}}
{{/if}}
</div>
<div class="modal-footer">

View file

@ -0,0 +1,22 @@
<div class='autocomplete'>
<ul>
{{#each options.users}}
<li>
<a href='#'>{{avatar this imageSize="tiny"}}
<span class='username'>{{this.username}}</span>
<span class='name'>{{this.name}}</span></a>
</li>
{{/each}}
{{#if options.groups}}
{{#if options.users}}<hr>{{/if}}
{{#each options.groups}}
<li>
<a href=''><i class='icon-group'></i>
<span class='username'>{{this.name}}</span>
<span class='name'>{{max-usernames usernames max="3"}}</span>
</a>
</li>
{{/each}}
{{/if}}
</ul>
</div>

View file

@ -173,7 +173,7 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
$LAB.script(assetPath('defer/html-sanitizer-bundle'));
ComposerView.trigger("initWmdEditor");
var template = this.container.lookupFactory('view:user-selector').templateFunction();
var template = this.container.lookup('template:user-selector-autocomplete.raw');
$wmdInput.data('init', true);
$wmdInput.autocomplete({
template: template,
@ -181,7 +181,7 @@ var ComposerView = Discourse.View.extend(Ember.Evented, {
return userSearch({
term: term,
topicId: self.get('controller.controllers.topic.model.id'),
include_groups: true
includeGroups: true
});
},
key: "@",

View file

@ -1,109 +0,0 @@
import TextField from 'discourse/components/text-field';
import userSearch from 'discourse/lib/user-search';
var compiled;
function templateFunction() {
if (!compiled) {
Handlebars.registerHelper("showMax", function(context, block) {
var maxLength = parseInt(block.hash.max) || 3;
if (context.length > maxLength){
return context.slice(0, maxLength).join(", ") + ", +" + (context.length - maxLength);
} else {
return context.join(", ");
}
});
compiled = Handlebars.compile(
"<div class='autocomplete'>" +
"<ul>" +
"{{#each options.users}}" +
"<li>" +
"<a href='#'>{{avatar this imageSize=\"tiny\"}} " +
"<span class='username'>{{this.username}}</span> " +
"<span class='name'>{{this.name}}</span></a>" +
"</li>" +
"{{/each}}" +
"{{#if options.groups}}" +
"{{#if options.users}}<hr>{{/if}}"+
"{{#each options.groups}}" +
"<li>" +
"<a href=''><i class='icon-group'></i>" +
"<span class='username'>{{this.name}}</span> " +
"<span class='name'>{{showMax this.usernames max=3}}</span>" +
"</a>" +
"</li>" +
"{{/each}}" +
"{{/if}}" +
"</ul>" +
"</div>");
}
return compiled;
}
var UserSelector = TextField.extend({
didInsertElement: function() {
var userSelectorView = this,
selected = [];
function excludedUsernames() {
var exclude = selected;
if (userSelectorView.get('excludeCurrentUser')) {
exclude = exclude.concat([Discourse.User.currentProp('username')]);
}
return exclude;
}
$(this.get('element')).val(this.get('usernames')).autocomplete({
template: templateFunction(),
disabled: this.get('disabled'),
single: this.get('single'),
allowAny: this.get('allowAny'),
dataSource: function(term) {
return userSearch({
term: term,
topicId: userSelectorView.get('topicId'),
exclude: excludedUsernames(),
include_groups: userSelectorView.get('include_groups')
});
},
transformComplete: function(v) {
if (v.username) {
return v.username;
} else {
var excludes = excludedUsernames();
return v.usernames.filter(function(item){
// include only, those not found in the exclude list
return excludes.indexOf(item) === -1;
});
}
},
onChangeItems: function(items) {
items = _.map(items, function(i) {
if (i.username) {
return i.username;
} else {
return i;
}
});
userSelectorView.set('usernames', items.join(","));
selected = items;
},
reverseTransform: function(i) {
return { username: i };
}
});
}
});
UserSelector.reopenClass({ templateFunction: templateFunction });
export default UserSelector;