In my app.js file I have this code :
myApp.directive("inventoryProduct", function ()
{
return {
restrict: "E",
scope : {
name : "#",
price: "#",
onReport:"&"
},
template: "<div><b>{{name}} costs {{ price}}$</b></div>\n<div>\n <button class=\"btn btn-lg btn-danger\" ng-click=\"onReport({IDontKnowA:name,IDontKnowB:price})\">Change name</button>\n</div>"
}
})
Looking at the template's value , It is a HTML string.
Question
Assuming I'm selecting the html string / any string —
Is there any option in Webstorm to : "Move selection to a new file" and having this new file name so that now the file will be :
myApp.directive("inventoryProduct", function ()
{
return {
restrict: "E",
scope : {
name : "#",
price: "#",
onReport:"&"
},
template: "newFile.js"
}
})
No, there are no such refactorings. Please feel free to create a request for this feature in youtrack. Related ticket: WEB-1232
Related
The best example to illustrate what I am trying to develop is a desktop email application.
On the left there is a vertical menu (on a quasar q-drawer).
Next, also on the left, there is a mailing list (on a quasar q-list within a q-drawer).
When each item is selected, the corresponding content is displayed on the right (on a quasar q-page).
Expected operation:
The list is loaded once and when I successively select the various items in the list, only the content on the right should be used and the content updated according to the id sent as a parameter in the request.
Note that the list component is only rendered once; that is, it is not rendered again each time a item is selected from the list and remains visible while the content is displayed on the right
The problem:
When I select the first item in the mailing list it works correctly and as expected, the mail content is displayed on the q-page.
When I select a second item from the list it doesn't work anymore and the following error is displayed on the console:
Uncaught (in promise) NavigationDuplicated {_name:
"NavigationDuplicated", name: "NavigationDuplicated", message:
"Navigating to current location ("/mailcontent") is not allowed",
stack: "Error at new NavigationDuplicated
(webpack-int…node_modules/vue/dist/vue.runtime.esm.js:1853:26)"}
I would appreciate suggestions on how to resolve this issue.
The following code is intended to illustrate the problem in the main part:
Routes: secondlayout is the child of another layout
const routes = [
{
path: "/index",
component: () => import("layouts/AppLayout.vue"),
children: [
{ path: "/home", component: () => import("pages/Home.vue") },
{
path: "secondlayout",
component: () => import("Layouts/MailsPlace.vue"),
children: [
{ path: "/mailcontent", name: 'mailcontent', component: () => import("pages/MailContent.vue") },
]
}
]
}
];
Second layout where the email application (list and content) is rendered with q-drawer and router-view
<template>
<q-layout view="lhh LpR lff" container class=" myclass shadow-2 window-height" >
<q-drawer
style="full-height"
v-model="drawerLeft"
:width="500"
:breakpoint="700"
elevated
content-class="bg-grey-1"
>
<q-scroll-area
class="fit"
style="margin-top:80px">
<q-list separator padding>
<q-separator />
<list-mails
v-for="(mail, index) in mails"
:mail="mail"
:key="mail.id_mail"
:id="index">
</list-mails>
<q-separator />
</q-list>
</q-scroll-area>
</q-drawer>
<q-page-container>
<router-view></router-view>
</q-page-container>
</template>
<script>
export default {
data () {
return {
mails: {},
drawerRight: false,
}
},
/* watch: {
$route(to, from) {
console.log('after', this.$route.path);
}
},
beforeRouteUpdate(to, from, next) {
console.log('before', this.$route.path);
next();
},*/
components: {
'list-mails': require("pages/ListMails.vue").default,
},
created: function() {
this.listMails()
},
methods: {
listMails(){
this.$axios.get("/listmails")
.then(response => {
if (response.data.success) {
this.mails = response.data.mails.data;
} else {
showErrorNotify('msg');
}
})
.catch(error => {
showErrorMessage(error.message);
});
}
}
</script>
Mail list item with mailitemclick method
<template>
<q-item
clickable
v-ripple
exact
#click="mailitemclick(mail.id_mail)"
>
<q-item-section>
<q-item-label side lines="2"> {{ mail.title_mail }}</q-item-label>
</q-item-section>
</q-item>
</template>
<script>
export default {
props: ["mail"],
methods:{
mailitemclick(id){
this.$router.push({
name: 'mailcontent',
params: {id:id}
});
}
}
}
</script>
Mail content
<template>
<q-page class="fit row wrap justify-center tems-start content-start" style="overflow: hidden;">
<div style="padding:5px; margin:0px 0px 20px 0px; min-width: 650px; max-width: 700px;" >
<q-item>
<q-item-label class="titulo"> {{ mail.title_mail }} </q-item-label>
<div v-html="mail.content_mail"></div>
</q-item>
</div>
</q-page>
</template>
<script>
export default {
name: 'mailcontent',
data() {
return {
mail: {},
};
},
created() {
this.$axios.get(`/mailcontent/${this.$route.params.id}`)
.then(response => {
if (response.data.success) {
this.mail = response.data.mail[0])
} else {
showErrorNotify('msg');
}
})
.catch(error => {
showErrorMessage(error.message);
});
}
}
</script>
This happened to me when I had a router-link pointing to the same route. e.g. /products/1.
The user is able to click on the products, but if a product was
already clicked (and the component view was already loaded) and the
user attempts to click it again, the error/warning shows in the
console.
You can solve this by adding catch block.
methods: {
mailitemclick(id) {
this.$router.push({
name: 'mailcontent',
params: {'id': id}
}).catch(err => {});
}
},
But in the mail-content, you need to use watch for calling function and in mounted for first-time calling.
Temp Example -
data() {
return {
mail: {},
test_mails: {
12: {
content_mail: '<div>test 12<div>'
},
122:{
content_mail: '<div>test 122<div>'
}
}
}
},
mounted() {
this.mail = this.test_mails[this.$route.params.id]
},
watch:{
'$route':function () {
this.mail = this.test_mails[this.$route.params.id]
}
}
OR
You can use :to in list-mail to avoild click and catch -
<q-item
clickable
v-ripple
exact
:to="'/mailcontent/'+mail.id_mail"
>
<q-item-section>
<q-item-label side lines="2"> {{ mail.title_mail }}</q-item-label>
</q-item-section>
</q-item>
children: [
{ path: '', component: () => import('pages/Index.vue') },
{
path: "secondlayout",
component: () => import("layouts/mail-place.vue"),
children: [
{ path: "/mailcontent/:id", name: 'mailcontent', component: () => import("pages/mail-content.vue") },
]
}
]
I'm struggling a bit with the proper pattern to use here. I have a model which represents a power selector called selector, each selector has a hasMany with selectorOption which makes up the options for the selector
I then have a dashboardItem model which loops over each selector and implements it.
route.js
export default Route.extend({
model(params) {
return RSVP.hash({
dashboard: get(this, 'store').findRecord('dashboard', params.dashboard_id),
selectors: get(this, 'store').findAll('selector'),
});
},
setupController(controller, models) {
controller.setProperties(models);
},
});
template.hbs
{{#each selectors as |selector|}}
<div class="column is-12 object-item">
<div class="card">
<header class="card-header">
<p class="card-header-title">
{{selector.title}}
</p>
</header>
<div class="card-content">
{{#power-select-multiple
placeholder="Vision"
options=selector.selectorOptions
searchEnabled=false
onchange=(action 'something...') as |option|}}
{{option.title}}
{{/power-select-multiple}}
</div>
</div>
</div>
{{/each}}
I'm not sure what to do on the onchange, either with a custom function or using built in tools of power-select.
Each selector is a multi-selector.
This works correctly to the point that I can create any number of selectors and they display on the front end with their correct options as expected.
How should I go about saving the options the users choose against the dashboardItem?
Here is a section from the database which shows the models and their relationships. Note there is currently no relationship between a selector and a dashboardItem (Maybe there should be though?)
{
"selectorOptions" : {
"-Kyc7on207d_IxnNw2iO" : {
"title" : "Apple",
"vision" : "-Kyc7nG9Bz3aEGLked8x"
},
"-Kyc7qC9_uxFgXP9c7hT" : {
"title" : "Orange",
"vision" : "-Kyc7nG9Bz3aEGLked8x"
},
"-Kyc7qqZPMikoG1r3r5g" : {
"title" : "Bannana",
"vision" : "-Kyc7nG9Bz3aEGLked8x"
},
"-Kyc7uZu8MTfUdH70cBR" : {
"title" : "Blue",
"vision" : "-Kyc7rtTPTMJxAPacg-L"
},
"-Kyc7vJC3ImzVOEraALx" : {
"title" : "Green",
"vision" : "-Kyc7rtTPTMJxAPacg-L"
},
"-Kyc7wCrqDz8CD_I-dYy" : {
"title" : "Red",
"vision" : "-Kyc7rtTPTMJxAPacg-L"
}
},
"selectors" : {
"-Kyc7nG9Bz3aEGLked8x" : {
"title" : "Fruits",
"selectorOptions" : {
"-Kyc7on207d_IxnNw2iO" : true,
"-Kyc7qC9_uxFgXP9c7hT" : true,
"-Kyc7qqZPMikoG1r3r5g" : true
}
},
"-Kyc7rtTPTMJxAPacg-L" : {
"title" : "Colours ",
"selectorOptions" : {
"-Kyc7uZu8MTfUdH70cBR" : true,
"-Kyc7vJC3ImzVOEraALx" : true,
"-Kyc7wCrqDz8CD_I-dYy" : true
}
}
}
}
The solution was to not fight against relationships with basic array storage.
For example
Base
export default Model.extend({
title: attr('string'),
visionOptions: hasMany('vision-option'),
});
Bases Options
export default Model.extend({
title: attr('string'),
vision: belongsTo('vision'),
});
The model to save the selected objects on
export default Model.extend({
//...
visionOptions: hasMany('vision-option', {async: true}),
//...
});
The component to handle saving, and selecting the correct objects
export default Component.extend({
tagName: "",
classNames: "",
selectedVisions: computed('dashboardItem.visionOptions', function () {
const visionId = this.get('vision.id');
const options = this.get('dashboardItem.visionOptions');
return options.filterBy('vision.id', visionId);
}),
actions: {
addVision(newList) {
let dashboardItem = get(this, 'dashboardItem');
let options = get(this, 'selectedVisions');
options.forEach(function (me) {
if (!newList.includes(me)) {
dashboardItem.get('visionOptions').removeObject(me);
}
});
newList.forEach(function (me) {
if (!options.includes(me)) {
dashboardItem.get('visionOptions').pushObject(me);
}
});
dashboardItem.save().then(() => {
dashboardItem.notifyPropertyChange('visionOptions')
});
}
}
});
Template to render power-select
{{#power-select-multiple
placeholder=""
options=vision.visionOptions
searchEnabled=false
selected=selectedVisions
onchange=(action 'addVision') as |vision|}}
{{vision.title}}
{{/power-select-multiple}}
This allows there to be an unknown number of "visions", with an unknown number of "visionObjects" to be loaded and saved.
The notifyPropertyChange is required to update the computed property so the frontend renders when a user adds or removes a selected object. This is only awkward because there isn't a direct known database key.
I am using rails and algolia gem with mongoid datastore.
I am sending data to algolia for a model Question. One of the doc example in Algolia system is
objectID: 5691e056410213a381000000
text: "what is #cool about your name Mr. John? #name #cool"
asked_to: ["565571704102139759000000", "i7683yiq7r8998778346q686", "kjgusa67g87y8e7qtwe87qwe898989"]
asked_by: "564a9b804102132465000000"
created_at: "2016-01-10T04:38:46.201Z"
card_url: "http://localhost:3000/cards/5691e056410213a381000000"
answerers: []
has_answer: false
requestor_count: 0
status: "active"
popularity_point: 0
created_at_i: 1452400726
_tags: ["cool", "name"]
I want to find all those documents, where it meets these two conditions:
1) text contains your name
2) asked_to contains i7683yiq7r8998778346q686
I am using Twitter's typeahead javascript library. And my UI's javascript to implement algolia search is as follows:
<input class="typeahead ui-widget form-control input-md search-box tt-input" id="typeahead-algolia" placeholder="Search questions" spellcheck="false" type="text" autocomplete="off" dir="auto" style="position: relative; vertical-align: top;">
$(document).on('ready page:load', function () {
var client = algoliasearch("APPLICATION_ID", "SEARCH_KEY");
var index = client.initIndex('Question');
$('#typeahead-algolia').typeahead(
{
hint: false,
highlight: true,
minLength: 1
},
{
source: index.ttAdapter({hitsPerPage: 10}),
displayKey: 'text'
}
).on('keyup', this, function (event) {
if (event.keyCode == 13) {
$('#typeahead-algolia').typeahead('close');
window.location.href = "/?keyword="+encodeURIComponent($('#typeahead-algolia').val());
}
});
$('.typeahead').bind('typeahead:select', function(ev, suggestion) {
window.location.href = suggestion.card_url;
});
});
So my question is:
This code works perfectly. But how to add condition for asked_to contains i7683yiq7r8998778346q686 in above javascript to filter out result.
You can use a facet filter on the asked_to attribute in your query.
You first need to declare the attribute asked_to as an attribute for faceting in your index settings and then pass asked_to:i7683yiq7r8998778346q686 as a facet filter in your query via the facetFiltersquery parameter.
When your index settings are changed, you can change your source to add the facetFilters parameter:
$('#typeahead-algolia').typeahead(
{
hint: false,
highlight: true,
minLength: 1
},
{
source: index.ttAdapter({hitsPerPage: 10, facetFilters: "asked_to:i7683yiq7r8998778346q686"}),
displayKey: 'text'
}
).on('keyup', this, function (event) {
if (event.keyCode == 13) {
$('#typeahead-algolia').typeahead('close');
window.location.href = "/?keyword="+encodeURIComponent($('#typeahead-algolia').val());
}
});
I have a site with stocks. I would like to add typeahead functionality to my bootstrap template. Since there are about 5000 stocks and will be even more in the future. I am using haystack with whoosh index. I should be using remote version of typeahead.js, but it is not working. Could you please take a look and tell me what am I missing?
<script type="text/javascript">
var stocks = new Bloodhound({
datumTokenizer: function (datum) {
return Bloodhound.tokenizers.whitespace(datum.name);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
limit: 5,
remote: {
url: "/search/autocomplete/",
replace: function(url, query) {
return url + "?q=" + query;
},
filter: function(stocks) {
return $.map(stocks, function(data) {
return {
tokens: data.tokens,
symbol: data.symbol,
name: data.name
}
});
}
}
});
stocks.initialize();
$('.typeahead').typeahead(null, {
name: 'stocks',
displayKey: 'name',
minLength: 1, // send AJAX request only after user type in at least X characters
source: stocks.ttAdapter()
});
</script>
This is my form
<form class="input-prepend" method="get" action="/search/">
<div id="remote">
<button type="submit" class="btn">Search</button>
<input type="text" class="typeahead" id="id_q" placeholder="Stock symbol or name" autocomplete="off" name="q">
</div>
</form>
Urls.py
url(r'^search/autocomplete/', 'stocks.views.autocomplete'),
url(r'^search/', include('haystack.urls')),
autocomplete view
from haystack.query import SearchQuerySet
import json
def autocomplete(request):
sqs = SearchQuerySet().autocomplete(content_auto=request.GET.get('q', ''))[:5]
array = []
for result in sqs:
data = {"symbol": str(result.symbol),
"name": str(result.name),
"tokens": str(result.name).split()}
array.insert(0, data)
return HttpResponse(json.dumps(array), content_type='application/json')
json response:
[{"tokens": ["Arbor", "Realty", "Trus"], "symbol": "ABR", "name": "Arbor Realty Trus"}, {"tokens": ["ABM", "Industries", "In"], "symbol": "ABM", "name": "ABM Industries In"}, {"tokens": ["AmerisourceBergen"], "symbol": "ABC", "name": "AmerisourceBergen"}, {"tokens": ["ABB", "Ltd", "Common", "St"], "symbol": "ABB", "name": "ABB Ltd Common St"}, {"tokens": ["Allianceberstein"], "symbol": "AB", "name": "Allianceberstein "}]
This is my domain name: digrin.com and this is autocomplete url.
What am I missing?
I can see two problems:
1) Your script declaration is missing a type attribute:
<script src="http://code.jquery.com/jquery-1.11.0.js"></script>
<script src="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.js"></script>
<script type='text/javascript' src="http://twitter.github.io/typeahead.js/releases/latest/typeahead.bundle.js"></script>
add "type='text/javascript'" to the script declarations for jquery and bootstrap.
A more modern way of declaring your script tags can be found here.
2) To initialise Typeahead you need to place the code into your jQuery ready method i.e.
$(function(){
var stocks = new Bloodhound({
datumTokenizer: function (datum) {
return Bloodhound.tokenizers.whitespace(datum.name);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
limit: 5,
remote: {
url: "/search/autocomplete/",
replace: function(url, query) {
return url + "?q=" + query;
},
filter: function(stocks) {
return $.map(stocks, function(data) {
return {
tokens: data.tokens,
symbol: data.symbol,
name: data.name
}
});
}
}
});
stocks.initialize();
$('.typeahead').typeahead(null, {
name: 'stocks',
displayKey: 'name',
minLength: 1, // send AJAX request only after user type in at least X characters
source: stocks.ttAdapter()
});
});
As it is currently the typeahead code wont get loaded.
I am working with backbone and handlebars for a month and I have this problem.
I have this object that I get from my model:
Object {list: Object, permissions: Object}
list: Object
0: Object
id: "1"
name: "cms"
__proto__: Object
1: Object
2: Object
3: Object
permissions: Object
analytics: true
categories: false
cms: true
coupons: false
Now with handlebars I'm trying to find out how I can checked the box when the relative permission value is true like this:
{{#each list}}
<tr>
<th><input type="checkbox" id="{{id}}"
{{#each permission}}
{{#ifCond this {{name}} }}//that's where my current error is when I try to precompile this template,the syntax is wrong
checked
{{/ifCond}}
{{/each}}
/>{{name}}
</th>
</tr>
{{/each}}
ifCond is a function that I builded in handlebars.js:
Handlebars.registerHelper('ifCond', function(v1, v2, options) {
if(v1 === v2) {
return options.fn(this);
}
return options.inverse(this);
});
Hope someone can help me out!!
Thank you!
This is how you would have written your template if there was no helper in the first place.
Start from this point
Without Helper
In the template you were trying to iterate over objects, which is not the right approach.
You should leave that to the template helper to take care of that passing the correct context.
---- This is again wrong ( something in these lines)
| compare="name"
{{#ifCond this {{name}} }}
^
^ ----- Context
|
Name of helper
under which it is
registered
When you pass a value to compare=name the value will be replaces by that key value of that object.
But remember that you are already inside each loop that iterates over list object.
So this will point to that object. So you need to go back to the parent object 1 step using ../ which will give you access to the permissions object.
{{#ifCond this compare=name }}
Will map to the arguments of the helper.
'ifCond', function(v1, v2, options) {
v1 will be this context
v2 will map to the handlebar options
options will be undefined as no 3rd argument was passed in the helper
So the template will be of this form
{{#ifCond this obj=../this compare=this.name}}
this - context of each (current object in loop)
obj= will be dumped into the options.hash object. It is the main object.
compare will be the name attribute inside this context
maps to
'ifCond', function (context, options)
So your template structure will look like below
Template
<script type="text/template" id="handlebar-template">
{{#each list}}
<tr>
<th>
<span>
<input type="checkbox" id="{{id}}"
{{#ifCond this obj=../this compare=this.name}}
checked
{{/ifCond}} />
</span>
<span class="name">{{name}}</span>
</th>
</tr>
{{/each}}
</script>
Helper
Handlebars.registerHelper('ifCond', function (context, options) {
var permissions = options.hash.obj.permissions,
name = options.hash.compare;
if (permissions[name]){
return options.fn(this);
}
return options.inverse(this);
});
Full Code
// The object in question
var obj = {
"list": [{
"id": 1,
"name": "cms"
}, {
"id": 2,
"name": "analytics"
}, {
"id": 3,
"name": "coupons"
}, {
"id": 4,
"name": "categories"
}],
"permissions": {
"analytics": true,
"categories": false,
"cms": true,
"coupons": false
}
};
// Model for the Object
var HandlebarModel = Backbone.Model.extend({});
var handlebarModel = new HandlebarModel(obj);
// View that will render the template inside the table
var HandlebarView = Backbone.View.extend({
el: $('#table'),
initialize: function () {
var source = $('#handlebar-template').html();
this.template = Handlebars.compile(source);
},
render: function () {
var $elem = $('tbody', this.$el);
$elem.empty();
$elem.append(this.template(this.model.toJSON()));
}
});
Handlebars.registerHelper('ifCond', function (context, options) {
console.log("this context " + this);
console.log("name -" + options.hash.compare);
console.log("Main Object - " + options.hash.obj);
var permissions = options.hash.obj.permissions,
name = options.hash.compare;
if (permissions[name]){
return options.fn(this);
}
return options.inverse(this);
});
var handlebarView = new HandlebarView({
model: handlebarModel
});
handlebarView.render();
Check Fiddle