How to dynamically set itemTpl based on the items in some other store in sencha touch? - sencha-touch

I have 2 stores. SortStore and WorkStore.
This is my WorkStore fields in the model:
{name: 'domainName',type:'string',mapping:'DomainName'},
{name: 'objectId',type:'string',mapping:'ObjectID'},
{name: 'serverName',type:'string',mapping:'ServerName'},
{name: 'sourceWorkset',type:'string',mapping:'SourceWorkset'},
{name: 'sourceWorkstep',type:'string',mapping:'SourceWorkstep'},
This is my SortStore fields in the model:
['name', 'value']
I want to display data from workstore in a list based on the items in the sortstore. if sort store has domain name and object id. The list should populate only those values from the workstore. Stores are getting dynamically loaded by webservice call.
{
xtype:'list',
id:'workitemlist',
// store:'WorkitemStore',
itemTpl:'<table><tr><td valign="top"><img src="{workitemImage}" width=20px height=22px />' +
' </td><td><span><b>{workitemName}</b></span> <br/>' +
'<span class="label">Object Id:</span> {objectId} <br /><span class="label">' +
'Source Workstep: </span>{sourceWorkstep}</td> </tr></table>'
}
Instead of printing all these in itemTpl I need only those present in sortstore to get populated. How to dynamically set this itemTpl based on the items in some other store. Any help is appreciated.

We can dynamically set itemTpl this way:
var template = '<table><tr><td valign="top"><img src="{Image}"' +
' width=20px height=22px />' +
' </td><td><span><b>{Name}</b></span> <br/>';
var myStore = Ext.getStore('RealStore');
if (myStore != null) {
myStore.each(function (record) {
if (record.get('field')) {
template += '<span class="label">Domain Name:</span>' +
' {field} <br/>';
}
}

Not sure if that's the best way but the way I have implemented it in past was to keep JSON of templateId(using differentiating field e.g. "tpl_"+field) & templateContent for my app and load it on startup in a variable(tpls). Once it is loaded I can refer it from anywhere like this:
var styleTemplate = eval('tpls.tpl_' + rec.field);
and then pass it to list like this:
{
xtype: 'list',
itemTpl : styleTemplate,
store : listStore,
id : 'ilid',
layout : 'fit'
}

Related

How to validate multiple user inputs within just one popup using Vue-SweetAlert2

As a coding training, right now I'm making a web page where you can click a "Create" button, which triggers a popup, where you are supposed to fill in 6 data inputs, whose input style varies like text, select etc. (See the code and the attached image below)
<template>
<v-btn class="create-button" color="yellow" #click="alertDisplay">Create</v-btn>
<br/>
<p>Test result of createCustomer: {{ createdCustomer }}</p>
</div>
</template>
<script>
export default {
data() {
return {
createdCustomer: null
}
},
methods: {
alertDisplay() {
const {value: formValues} = await this.$swal.fire({
title: 'Create private customer',
html: '<input id="swal-input1" class="swal2-input" placeholder="Customer Number">' +
'<select id="swal-input2" class="swal2-input"> <option value="fi_FI">fi_FI</option> <option value="sv_SE">sv_SE</option> </select>'
+
'<input id="swal-input3" class="swal2-input" placeholder="regNo">' +
'<input id="swal-input4" class="swal2-input" placeholder="Address">' +
'<input id="swal-input5" class="swal2-input" placeholder="First Name">' +
'<input id="swal-input6" class="swal2-input" placeholder="Last Name">'
,
focusConfirm: false,
preConfirm: () => {
return [
document.getElementById('swal-input1').value,
document.getElementById('swal-input2').value,
document.getElementById('swal-input3').value,
document.getElementById('swal-input4').value,
document.getElementById('swal-input5').value,
document.getElementById('swal-input6').value
]
}
})
if (formValues) {
this.createdCustomer = this.$swal.fire(JSON.stringify(formValues));
console.log(this.createdCustomer);
}
}
}
}
</script>
Technically, it's working. The popup shows up when the "create" button is clicked, and you can fill in all the 6 blanks and click the "OK" button as well. But I want to add some functionalities that check if the user inputs are valid, I mean things like
address should be within 50 characters
firstName should be within 20 characters
customerNumber should include both alphabets and numbers
and so on.
If it were C or Java, I could probably do something like
if(length <= 50){
// proceed
} else {
// warn the user that the string is too long
}
, but when it comes to validating multiple inputs within a single popup using Vue-SweetAlert2, I'm not sure how to do it, and I haven't been able to find any page that explains detailed enough.
If it were just a single input, maybe you could use inputValidor like this
const {value: ipAddress} = await Swal.fire({
title: 'Enter an IP address',
input: 'text',
inputValue: inputValue,
showCancelButton: true,
inputValidator: (value) => {
if (!value) {
return 'You need to write something!'
}
}
})
if (ipAddress) {
Swal.fire(`Your IP address is ${ipAddress}`)
}
, but this example only involves "one input". Plus, what this checks is just "whether an IP address has been given or not" (, which means whether there is a value or not, and it doesn't really check if the length of the IP address is correct and / or whether the input string consists of numbers / alphabets).
On the other hand, what I'm trying to do is to "restrict multiple input values (such as the length of the string etc)" "within a single popup". Any idea how I am supposed to do this?
Unfortunately the HTML tags to restrict inputs (e.g. required, pattern, etc.) do not work (see this issues),
so I find two work around.
Using preConfirm as in the linked issues
You could use preConfirm and if/else statement with Regex to check your requirement, if they are not satisfied you could use Swal.showValidationMessage(error).
const{value:formValue} = await Swal.fire({
title: "Some multiple input",
html:
<input id="swal-input1" class="swal-input1" placeholder="name"> +
<input id="swal-input2" class="swal-input2" placeholder="phone">,
preConfirm: () => {
if($("#swal-input1").val() !== "Joe"){
Swal.showValidationMessage("your name must be Joe");
} else if (!('[0-9]+'.test($("#swal-input2").val())){
Swal.showValidationMessage("your phone must contain some numbers");
}
}
});
Using Bootstrap
In this way Bootstrap does the check at the validation, you need to include class="form-control" in your input class and change a little your html code.
If some conditions fails, the browser shows a validation message for each fields, in the order they are in the html code.
const {value: formValue} = await Swal.fire({
title: 'Some multiple inputs',
html:
'<form id="multiple-inputs">' +
'<div class="form-group">' +
'<input type="text" class="form-control swal-input1" id="swal-input1" min=2 max=4 required>' +
'</div>' +
'<div class="form-group">' +
'<input type="text" class="form-control swal-input2" id="swal-input2" placeholder="Name" pattern="[A-Za-z]" required>' +
'</div>' +
'</form>',
});
I have tried both the solution, actually only with Bootstrap3 but it should work also with the latest release.

How to dynamically populate a Picker using Alloy with Appcelerator?

Is there a way to populate a picker dynamically? This works for option dialogs:
view.xml:
<OptionDialog id="distributionPointsOptionDialog">
<Options>
<Option id="{dp}"></Option>
</Options>
</OptionDialog>
controller.js:
var dp = [];
for (var j in _data) {
if (_data[j].country == country) {
dp.push(_data[j].city + "-" + _data[j].dp_name);
}
}
$.distributionPointsOptionDialog.options = dp;
Is there a way to do the same for a multi-column picker? I tried unsuccessfully to populate a single-column picker.
controller.js:
var fs = Ti.Filesystem;
var installedFonts = fs.getFile(fs.resourcesDirectory+ "/fonts").getDirectoryListing();
Ti.API.info("list of resourcesDirectory fonts: " + JSON.stringify(installedFonts));
var fonts = [];
for (var j in installedFonts) {
fonts.push(installedFonts[j]);
}
$.testPicker.column = fonts;
views.xml:
<Picker id="testPicker" selectionIndicator="true" height="Ti.UI.SIZE" width="70%" visible="true" zIndex="200" useSpinner="true">
<Column>
<Row title="{fonts}"></Row>
</Column>
</Picker>
This results in an error:
ERROR] : message = "undefined is not an object (evaluating '$model.__transform')";
There are couple of wrong things with your implementation.
useSpinner property has been deprecated, so please remove that from your code.
The way you are implementing Options & Pickers is a process of Alloy-Model binding.
For dynamic Options, you can simply use below code:
<OptionDialog id="distributionPointsOptionDialog"></OptionDialog>
$.distributionPointsOptionDialog.options = ['Helvetica', 'Arial', 'Times New Roman'];
<Option id="{dp}"></Option> - this syntax is used for Model data binding
For Picker, use below code:
var fonts = [];
fonts[0]=Ti.UI.createPickerRow({title:'Helvetica'});
fonts[1]=Ti.UI.createPickerRow({title:'Arial'});
fonts[2]=Ti.UI.createPickerRow({title:'Times New Roman'});
fonts[3]=Ti.UI.createPickerRow({title:'Georgia'});
$.testPicker.add(fonts);
OR
var column = Ti.UI.createPickerColumn();
column.add( Ti.UI.createPickerRow({ title: 'Helvetica' }) );
column.add( Ti.UI.createPickerRow({ title: 'Arial' }) );
column.add( Ti.UI.createPickerRow({ title: 'Times New Roman' }) );
column.add( Ti.UI.createPickerRow({ title: 'Georgia' }) );
$.testPicker.columns = [column];
For Pickers, in short, you can either use $.testPicker.add() method, or you can set columns like this $.testPicker.columns = [column1, column2, column1, ...]
Read more here How to add Columns or Rows in Pickers
or
How to add column(s) in Picker

Product autocomplete input on module (Prestashop)

I'm developing a prestashop module that has to make lists of existing products.
For the configuration panel of the module, using renderForm() and getContent(), I'm trying to replicate the "accesories" capability, where you start writing some info of a product on an input, and it shows the products that are a match. When selecting that product, it gets added on a list. Like this:
This a screenshot of Catalog / Products / Associations tab.
I'm trying with PS 1.6.0.14 and PS1.6.1.0RC3. How would I replicate this functionality to get lists of products on a module configuration panel?
I tried looking here Prestashop AdminProductsController.php but I don't really understand where half of that info is coming from.
There is an autocomplete plugin in prestashop you got to use that for this. Its in js->jquery->plugins you got to add this plugin into your module to make it work.
I think that to achieve that functionality, the renderForm() function won't be enough since you have to bind some javascript and some custom html.
The process of writing a fully functional module is a bit long but by taking the accessories functionality as a starting point it wont be so hard and you will always have a reference on "how-to-do-it".
I would go with this:
1) first create your
getContent()
function to be able to show the custom template and the product associated by your module so we will have something along:
public function getContent(){
//post process part to save the associations
if(Tools::isSubmit('saveMyAssociations'){
... //we will see it later
}
$my_associations = MyModule::getAssociationsLight($this->context->language->id,Tools::getValue('id_product')); //function that will retrieve the array of all the product associated on my module table.
$this->context->smarty->assign(array(
'my_associations' => $my_associations,
'product_id' => (int)Tools::getValue('id_product')
));
return $this->display(__FILE__, 'views/templates/admin/admintemplate.tpl'); //custome template to create the autocomplete
}
//our little function to get the already saved list, for each product we will retrieve id, name and reference with a join on the product/product_lang tables.
public static function getAssociationsLight($id_lang, $id_product, Context $context = null)
{
if (!$context)
$context = Context::getContext();
$sql = 'SELECT p.`id_product`, p.`reference`, pl.`name`
FROM `'._DB_PREFIX_.'my_associations`
LEFT JOIN `'._DB_PREFIX_.'product` p ON (p.`id_product`= `id_product_2`)
'.Shop::addSqlAssociation('product', 'p').'
LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (
p.`id_product` = pl.`id_product`
AND pl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl').'
)
WHERE `id_product_1` = '.(int)$id_product;
return Db::getInstance()->executeS($sql);
}
2) create a template that will be able to show the automplete and the list.
Here we will loop trough the saved associations to create our autocomplete list, and we will do it with some hidden field to keep track of the ids/name and also a visible list were we will have a delete button for each row.
<input type="hidden" name="inputMyAssociations" id="inputMyAssociations" value="{foreach from=$my_associations item=accessory}{$accessory.id_product}-{/foreach}" />
<input type="hidden" name="nameMyAssociations" id="nameMyAssociations" value="{foreach from=$my_associations item=accessory}{$accessory.name|escape:'html':'UTF-8'}¤{/foreach}" />
<div id="ajax_choose_product_association">
<div class="input-group">
<input type="text" id="product_autocomplete_input_association" name="product_autocomplete_input_association" />
<span class="input-group-addon"><i class="icon-search"></i></span>
</div>
</div>
<div id="divMyAssociations">
{foreach from=$my_associations item=accessory}
<div class="form-control-static">
<button type="button" class="btn btn-default delAssociation" name="{$accessory.id_product}">
<i class="icon-remove text-danger"></i>
</button>
{$accessory.name|escape:'html':'UTF-8'}{if !empty($accessory.reference)}{$accessory.reference}{/if}
</div>
{/foreach}
</div>
<input type="submit" name="submitMyAssociations" id="submitMyAssociations" value="Send"/>
<input type="hidden" name="productId" id="productId" value="{$product_id|escape:'html'}"/>
3) Now we can add the javascript to bind an autocomplete on the main input and perform all the logic for each action
$(document).ready(function(){
//our function wrapper.
var initMyAssociationsAutocomplete = function (){
//initialize the autocomplete that will point to the default ajax_products_list page (it returns the products by id+name)
$('#product_autocomplete_input_association')
.autocomplete('ajax_products_list.php', {
minChars: 1,
autoFill: true,
max:20,
matchContains: true,
mustMatch:true,
scroll:false,
cacheLength:0,
formatItem: function(item) {
return item[1]+' - '+item[0];
}
}).result(addAssociation);
//as an option we will add a function to exclude a product if it's already in the list
$('#product_autocomplete_input_association').setOptions({
extraParams: {
excludeIds : getAssociationsIds()
}
});
};
//function to exclude a product if it exists in the list
var getAssociationsIds = function()
{
if ($('#inputMyAssociations').val() === undefined)
return '';
return $('#inputMyAssociations').val().replace(/\-/g,',');
}
//function to add a new association, adds it in the hidden input and also as a visible div, with a button to delete the association any time.
var addAssociation = function(event, data, formatted)
{
if (data == null)
return false;
var productId = data[1];
var productName = data[0];
var $divAccessories = $('#divCrossSellers');
var $inputAccessories = $('#inputMyAssociations');
var $nameAccessories = $('#nameMyAssociations');
/* delete product from select + add product line to the div, input_name, input_ids elements */
$divAccessories.html($divAccessories.html() + '<div class="form-control-static"><button type="button" class="delAssociation btn btn-default" name="' + productId + '"><i class="icon-remove text-danger"></i></button> '+ productName +'</div>');
$nameAccessories.val($nameAccessories.val() + productName + '¤');
$inputAccessories.val($inputAccessories.val() + productId + '-');
$('#product_autocomplete_input_association').val('');
$('#product_autocomplete_input_association').setOptions({
extraParams: {excludeIds : getAssociationsIds()}
});
};
//the function to delete an associations, delete it from both the hidden inputs and the visible div list.
var delAssociations = function(id)
{
var div = getE('divMyAssociations');
var input = getE('inputMyAssociations');
var name = getE('nameMyAssociations');
// Cut hidden fields in array
var inputCut = input.value.split('-');
var nameCut = name.value.split('¤');
if (inputCut.length != nameCut.length)
return alert('Bad size');
// Reset all hidden fields
input.value = '';
name.value = '';
div.innerHTML = '';
for (i in inputCut)
{
// If empty, error, next
if (!inputCut[i] || !nameCut[i])
continue ;
// Add to hidden fields no selected products OR add to select field selected product
if (inputCut[i] != id)
{
input.value += inputCut[i] + '-';
name.value += nameCut[i] + '¤';
div.innerHTML += '<div class="form-control-static"><button type="button" class="delAssociation btn btn-default" name="' + inputCut[i] +'"><i class="icon-remove text-danger"></i></button> ' + nameCut[i] + '</div>';
}
else
$('#selectAssociation').append('<option selected="selected" value="' + inputCut[i] + '-' + nameCut[i] + '">' + inputCut[i] + ' - ' + nameCut[i] + '</option>');
}
$('#product_autocomplete_input_association').setOptions({
extraParams: {excludeIds : getAssociationsIds()}
});
};
//finally initialize the function we have written above and create all the binds.
initMyAssociationsAutocomplete();
//live delegation of the deletion button to our delete function, this will allow us to delete also any element added after the dom creation with the ajax autocomplete.
$('#divMyAssociations').delegate('.delAssociation', 'click', function(){
delAssociations($(this).attr('name'));
});
});
4) now you just need to save the associations made by your module autocomplete, and i suggest to perform it by first deleting any association made on a given product and then saving all of them. so you don't have to care about inserting or updating an entry
public function getContent(){
//post process part
if(Tools::isSubmit('saveMyAssociations'){
$product_id = (int)Tools::getValue('productId');
// see the function below, a simple query to delete all the associations on a product
$this->deleteMyAssociations($product_id);
if ($associations = Tools::getValue('inputMyAssociations'))
{
$associations_id = array_unique(explode('-', $associations));
if (count($associations_id))
{
array_pop($associations_id);
//insert all the association we have made.
$this->changeMyAssociations($associations_id, $product_id);
}
}
}
}
protected function deleteMyAssociations($product_id){
return Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'my_associations` WHERE `id_product_1` = '.(int)$product_id);
}
protected function changeMyAssociations($associations_id, $product_id){
foreach ($associations_id as $id_product_2)
Db::getInstance()->insert('my_associations', array(
'id_product_1' => (int)$product_id,
'id_product_2' => (int)$id_product_2
));
}
I hope it can help you to go through all of this.

typeahead bootstrap3: how to pass a parameter into remote?

I am trying to use typeahed twitter for bootstrap
https://github.com/twitter/typeahead.js
I need to change the remote dynamically according to what user types in and ANOTHER parameter.
(The goal is to retrieve the cities of a country)
trying with country="en"; does not affect it
trying with autocompleter.remote=".."; does not work.
Any idea ?
<script>
var country="fr";
var autocompleter = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: 'ajax.php?action=getVilles&country='+country+'&ville=%QUERY'
});
autocompleter.initialize();
$('#city').typeahead(null, {
name: 'city',
displayKey: 'city',
source: autocompleter.ttAdapter()
});
</script>
This is what I've done:
var tagStudies = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('text'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: "autocomplete/study",
replace: function(url, uriEncodedQuery) {
study = $('#study').val();
ssp = $("#social-security").val();
return url + '?q=' + uriEncodedQuery + '&s=' + study + '&ssp=' + ssp
},
filter: function (parsedResponse) {
return parsedResponse.studies;
}
}
});
Take a look at the replace function. The first parameter is the url, and the second is what the user is typing. I made a concatenation to pass the query and 2 extra params.
If you copy the code, make sure you replace 'text' for 'value' in datumTokenizer. Hope it helps

Ember.js input fields

Is it possible to use standard HTML5 input fields in an Ember.js view, or are you forced to use the limited selection of built in fields like Ember.TextField, Ember.CheckBox, Ember.TextArea, and Ember.select? I can't seem to figure out how to bind the input values to the views without using the built in views like:
Input: {{view Ember.TextField valueBinding="objectValue" }}
Specifically, I'm in need of a numeric field. Any suggestions?
EDIT: This is now out of date you can achieve everything above with the following:
{{input value=objectValue type="number" min="2"}}
Outdated answer
You can just specify the type for a TextField
Input: {{view Ember.TextField valueBinding="objectValue" type="number"}}
If you want to access the extra attributes of a number field, you can just subclass Ember.TextField.
App.NumberField = Ember.TextField.extend({
type: 'number',
attributeBindings: ['min', 'max', 'step']
})
Input: {{view App.NumberField valueBinding="objectValue" min="1"}}
#Bradley Priest's answer above is correct, adding type=number does work. I found out however that you need to add some attributes to the Ember.TextField object if you need decimal numbers input or want to specify min/max input values. I just extended Ember.TextField to add some attributes to the field:
//Add a number field
App.NumberField = Ember.TextField.extend({
attributeBindings: ['name', 'min', 'max', 'step']
});
In the template:
{{view App.NumberField type="number" valueBinding="view.myValue" min="0.0" max="1.0" step="0.01" }}
et voile!
Here is my well typed take on it :
App.NumberField = Ember.TextField.extend({
type: 'number',
attributeBindings: ['min', 'max', 'step'],
numericValue: function (key, v) {
if (arguments.length === 1)
return parseFloat(this.get('value'));
else
this.set('value', v !== undefined ? v+'' : '');
}.property('value')
});
I use it that way:
{{view App.NumberField type="number" numericValueBinding="prop" min="0.0" max="1.0" step="0.01" }}
The other systems where propagating strings into number typed fields.
You may also wish to prevent people from typing any old letters in there:
App.NumberField = App.TextField.extend({
type: 'number',
attributeBindings: ['min', 'max', 'step'],
numbericValue : function (key,v) {
if (arguments.length === 1)
return parseFloat(this.get('value'));
else
this.set('value', v !== undefined ? v+'' : '');
}.property('value'),
didInsertElement: function() {
this.$().keypress(function(key) {
if((key.charCode!=46)&&(key.charCode!=45)&&(key.charCode < 48 || key.charCode > 57)) return false;
})
}
})
Credit where its due: I extended nraynaud's answer
This is how I would do this now (currently Ember 1.6-beta5) using components (using the ideas from #nraynaud & #nont):
App.NumberFieldComponent = Ember.TextField.extend
tagName: "input"
type: "number"
numericValue: ((key, value) ->
if arguments.length is 1
parseFloat #get "value"
else
#set "value", (if value isnt undefined then "#{value}" else "")
).property "value"
didInsertElement: ->
#$().keypress (key) ->
false if (key.charCode isnt 46) and (key.charCode isnt 45) and (key.charCode < 48 or key.charCode > 57)
Then, to include it in a template:
number-field numericValue=someProperty