How to reaload data in Fuel UX Tree | jQuery - javascript

I use Fuel UX Tree framework for loading tree view data.
On first load of document, tree view renders correctly, but when I try to reload tree with new DataSource, I get nothing.
Here is my code example:
Template for tree UI:
<div id="MyTree" class="tree tree-plus-minus tree-no-line tree-unselectable">
<div class="tree-folder" style="display:none;">
<div class="tree-folder-header">
<i class="fa fa-folder"></i>
<div class="tree-folder-name">
</div>
</div>
<div class="tree-folder-content">
</div>
<div class="tree-loader" style="display:none">
</div>
</div>
<div class="tree-item" style="display:none;">
<i class="tree-dot"></i>
<div class="tree-item-name">
</div>
</div>
</div>
Tree UI init function:
var UITree = function () {
return {
//main function to initiate the module
init: function (el, data) {
if (typeof el != 'undefined' && typeof data == 'object') {
var DataSourceTree = function (options) {
this._data = options.data;
this._delay = options.delay;
};
DataSourceTree.prototype = {
data: function (options, callback) {
var self = this;
var data = $.extend(true, [], self._data);
callback({ data: data });
}
};
var treeDataSource = new DataSourceTree(data);
$(el).tree({
selectable: false,
dataSource: treeDataSource,
loadingHTML: '<img src="assets/img/input-spinner.gif"/>',
});
}
}
};
}();
.
After loading data with Ajax I call init function:
//my data from ajax result
var myData = {
"data": [
{
"name": "some_name",
"type": "item"
},
{
"name": "some_name_2",
"type": "item"
}
]
};
// call tree init to render data
UITree.init($('#MyTree'), myData);
Function works without error and renders tree only on first load of page, not on next Ajax call.

Finally I found solution. Maybe it's not the best practice, but this is only way what I found:
Looks like Fuelux Tree UI doesn't work when element already has assigned data property, so I made this:
// 1. Clear MyTree wrapper template with:
$('#MyTree .tree-item:visible').remove();
// 2. remove assigned data from template element object
delete($('#MyTree').data().tree);
// 3. Refactor Tree UI
$('#MyTree').tree({
selectable: false,
dataSource: {
data: function(options, callback) {
callback(MyData);
}
}
});
I searched all over internet, re-read Fuelux documentation, but nothing is mentioned about re-factoring Tree UI, so if anyone will meet same problem, this solution can help.

Related

How to use List.JS with Vue + Axios?

I have used List.JS before successfully, but this time I'm trying to use it with a Vue.JS rendering of a list from JSON data.
I have a button at the top that when clicked should show only the QB position player.
Unfortunately I just get nothing, all list items are removed and I don't get an error in the console so I'm not sure how to diagnose this.
Could it have something to do with the fact that the list elements aren't prerendered/static html but injected using vue.js?
https://jsfiddle.net/nolaandy/hw2mheem/
HTML/Vue Template
<div id='app'>
<div class="all-players-wrapper" id="all-player-listings">
<button id="filter-qb">QB</button>
<ul class="list">
<li v-for="player in playerJSON">
<div class="player-listing">
<div class="player-left">
<div class="player-name">{{player.firstName}} {{player.lastName}}</div>
<div class="playerPosition">{{ player.Position }}</div>
</div><!-- end player-left -->
<div class="player-right">
<div class="player-grade">GRADE <span>{{player.NFLGrade}}</span></div>
</div> <!--end player-right -->
</div>
</li>
</ul>
</div>
</div>
JS
var vm = new Vue({
el: '#app',
data: {
status: 'Combine Particpants',
playerJSON: []
},
created: function () {
this.loadData();
},
methods: {
loadData: function () {
var self = this;
axios.get('https://s3-us-west-2.amazonaws.com/s.cdpn.io/500458/tiny.json').then(function (response) {
self.playerJSON = response.data
console.log(response.data);
})
.catch(function (error) {
self.status = 'An error occurred - ' + error
});
}
}
});
var options = {
valueNames: [ 'playerPosition' ]
};
var featureList = new List('all-player-listings', options);
$('#filter-qb').click(function() {
featureList.filter(function(item) {
if (item.values().playerPosition == "QB") {
return true;
} else {
return false;
}
});
return false;
});
As you suspected, List.js isn't going to work properly if the DOM changes unpredictably. In this case, axios makes its call and populates the data after the (empty) List has been read into featureList.
Your example would work if you put the list-selecting-and-filtering code in the resolution of the axios call, but that's not going to be a solution that works in a truly dynamic environment.
A custom directive will be called every time the DOM updates, so you can apply your adjustments consistently. Here's a directive to apply a filter using List.js:
directives: {
filteredList(el, binding) {
if (binding.value) {
const options = {
valueNames: ['playerPosition']
};
const featureList = new List(el, options);
featureList.filter((item) => item.values().playerPosition === binding.value);
}
}
}
Apply it like so:
<div class="all-players-wrapper" v-filtered-list="filterValue">
Add the filterValue data item, and have the button set it:
<button id="filter-qb" #click="() => filterValue='QB'">QB</button>
and you're in business.
It's worth noting that you could get the same effect by using a computed to filter the data, and you wouldn't need an external library.
Updated fiddle

Vue v-links in JSON data

I am relatively new to Vue, so forgive me if this is obvious (or obviously impossible).
I have a set of JSON data (fetched from a RESTful API via vue-resource):
{content: "This is content. <a href='/blog'> Link to blog </a>"}
Right now, the link triggers a page reload. If it were a vue-router v-link, that would not be an issue. However, this doesn't work (quotes are escaped in the data, of course):
{content: "This is content. <a v-link="{ path: '/blog' }"> Link to blog </a>"}
At this point, the template is already parsed, and Vue won't create a v-link anymore (it will just show up as a v-link in the rendered html).
My final result would ideally mean that I could include links in my CMS, either in HTML or Vue format, and have Vue route them correctly as v-links.
Is there something I can do to make Vue interpret the link in the JSON data?
I've answered the question on Vue Chat, and writing it here in case any other people facing similar problem
Simplified example on Codepen
HTML
<div id="app">
<div>
<a v-link= "{path:'/home'}">Go to home</a>
</div>
<router-view></router-view>
</div>
<template id="home">
<div>
<div>
Fetched Content:
</div>
<div>
{{{ fetchedContent }}}
</div>
</div>
</template>
<template id="route1">
<div>
Route1 view
</div>
</template>
<template id="route2">
<div>
Route2 view, this is different from Route1
</div>
</template>
javascript
function getContent (callback) {
var content = 'Click this: Go to route1 and Go to route2'
setTimeout(function () { callback(content) }, 1000)
}
var Home = Vue.component('home',{
template:'#home',
data: function () {
return {
fetchedContent: 'Loading...'
};
},
ready: function () {
var self = this
var router = this.$router
getContent( function (result) {
self.fetchedContent = result;
Vue.nextTick(function () {
var hyperLinks = self.$el.getElementsByTagName('a')
Array.prototype.forEach.call(hyperLinks, function (a) {
a.onclick = function (e) {
e.preventDefault()
router.go({ path: a.getAttribute("href") })
}
})
})
})
}
});
var Route1 = Vue.component('route1', {
template: '#route1'
});
var Route2 = Vue.component('route2', {
template: "#route2"
});
var router = new VueRouter({
hashbang:false,
history:true
});
router.map({
'/home':{
component:Home
},
'/route1':{
component:Route1
},
'/route2':{
component:Route2
}
});
router.start({
}, '#app');
I had a similar solution here: question using a custom dataset in my JSON code and a click listener to process it:
mounted() {
window.addEventListener('click', event => {
let target = event.target
if (target && target.href && target.dataset.url) {
event.preventDefault();
console.log(target.dataset.url);
const url = JSON.parse(target.dataset.url);
console.log(url.name)
this.$router.push(url.name);
}
});
},

Angular, queuing ng-init

Is it possible queuing ng-init?
Generally, in first init I want to add JSON file to prototype vars (array) and in another init depending on the params I want to skip getJsonData() or add other JSON file to prototype.
function init(param) {
console.log("startInit");
// big JSON file
var promise = getJSON(param);
return promise.then( function() {
//some func
console.log("finish");
return true;
});
};
function getJSON(param) {
var deferred = $q.defer();
console.log("startInitDataInner");
someService.getJsonData(param).then(function(data) {
// some code
console.log("endInitDataInner");
deferred.resolve();
}, function(error) {
deferred.reject();
});
return deferred.promise;
};
in view ng-init
ng-init="init(param)"
ng-init="init(param)"
// ...
and log:
startInit
startInitDataInner
startInit
startInitDataInner
endInitDataInner
finish
endInitDataInner
finish
//..
Edit:
Generally, I want to create something like plugin in jQuery. I have this code:
<div ng-controller="parentController as parent">
<div ng-controller="childController as child" ng-init="child.init(parent.data)"></div>
</div>
<div ng-controller="parentController as parent">
<div ng-controller="childController as child" ng-init="child.init(parent.data2)"></div>
</div>
and configurable part by user:
angular.module('myApp').controller('parentController', ['$scope', function($scope) {
this.data = {
config: {
lang: "en",
title: "title"
}
};
this.data2 = {
config: {
lang: "pl",
title: "title2"
}
};
}]);
ng-init update api:
angular.extend(this, parent.data);
Do you have any ideas how I should do it differently?
Well, if you are working with angular, you use controllers. What is controller itslfmin general meaning? Right, its a constructor function. The main word here is function. What does function in general? Run the code inside.
So, just place your initial logic at the beggining of controller code (but without wrapping it as a separate function) and it will run just in time your controller will be resolved by angular resolver.
var controller = function () {
// vars, costs, etc.
console.log("startInit");
// big JSON file
var promise = getJSON(param);
return promise.then( function() {
//some func
console.log("finish");
return true;
});
};

Item selection MVC view with KnockoutJS

I am trying to implement a generic ASP.net MVC view. The UI should display a list of available and selected items loading data (basically list of string) from server. User can make changes into the list i.e. can select new items from available item list and also can remove items from selected list.
I wanted to do it using KnockoutJS as to take advantage of binding.
I manage to complete it upto the point everything is working except showing selected item as checked when the view is initialized in available list. E.g. As Shown Here
I tried various options (using template (closest to what I want to achieve), Checked attr, possible options), the issue is if I manage to display item checked some other functionality breaks. Tried defining a template but could not get it to work in my case.
HTML:
<div class='moverBoxOuter'>
<div id='contactsList'>
<span data-bind="visible: availableItems().length > 0">Available countries: </span>
<ul data-bind="foreach: availableItems, visible: availableItems().length > 0">
<li>
<input type="checkbox" data-bind="checkedValue: $data, checked: $root.selectedItems" />
<span data-bind="text: title"></span>
</li>
</ul>
<span data-bind="visible: selectedItems().length > 0">Selected countries: </span>
<ul data-bind="foreach: selectedItems, visible: selectedItems().length > 0">
<li>
<span data-bind="text: title"></span>
Delete
</li>
</ul>
</div>
JS:
var initialData = [
{
availableItems: [
{ title: "US", isSelected: true },
{ title: "Canada", isSelected: false },
{ title: "India", isSelected: false }]
},
{
selectedItems: [
{ "title": "US" },
{ "title": "Canada" }
]
}
];
function Item(titleText, isSelected) {
this.title = ko.observable(titleText);
this.isSelected = ko.observable(isSelected);
}
var SelectableItemViewModel = function (items) {
// Data
var self = this;
self.availableItems = ko.observableArray(ko.utils.arrayMap(items[0].availableItems, function (item) {
return new Item(item.title, item.isSelected);
}));
self.selectedItems = ko.observableArray(ko.utils.arrayMap(items[1].selectedItems, function (item) {
return new Item(item.title, item.isSelected);
}));
// Operations
self.selectItem = function (item) {
self.selectedItems.push(item);
item.isSelected(!item.isSelected());
};
self.removeItem = function (removedItem) {
self.selectedItems.remove(removedItem);
$.each(self.availableItems, function (item) {
if (item.title === removedItem.title) {
item.isSelected = false;
}
});
};
}
var vm = new SelectableItemViewModel(initialData);
$(document).ready(function () {
ko.applyBindings(vm);
});
Could you please help, see jsfiddle below:
http://jsfiddle.net/sbirthare/KR4a6/6/
**Update: Follow up question below **
Its followup question:
I need to add a combobox on same UI e.g. for US state. The available items are counties, based on user selection in state combo I need to filter out counties. I am getting data from server using AJAX and its all successful BUT the displayed list is not refreshing. I was expecting having binding setup correctly, if we change the observable array in viewmodel, the UI should change. I tried forcing change to availableItems but it just display all items. Please see if you can spot the problem in below code where I am updating ViewModel observable array.
function multiselect_change() {
console.log("event: openmultiselect_change");
var selectedState = $("#stateDropdownSelect").val();
var propertyName = $("#PropertyName").val();
var searchId = #Model.SearchId;
var items;
var model = { propertyName: propertyName, searchId: searchId, stateName: selectedState };
$.ajax({
url: '#Url.Action("GetFilterValues", "Search")',
contentType: 'application/json; charset=utf-8',
type: 'POST',
dataType: 'html',
data: JSON.stringify(model)
})
.success(function(result) {
debugger;
items = JSON.parse(result);
vm.availableItems(items.AvailableItems);
//vm.availableItems.valueHasMutated();
//var item = document.getElementById('availableItemId');
//ko.cleanNode(item);
//ko.applyBindings(vm, item);
vm.filter(selectedState);
})
.error(function(xhr, status) {
alert(status);
});
}
As user3426870 mentioned, you need to change the value you passed to the checked binding to boolean.
<input type="checkbox" data-bind="checkedValue: $data, checked: isSelected" />
Also, I don't think you need to have selectedItems in the initial data.
Instead in the viewModel, you can do something like:
self.selectedItems = ko.computed(function() {
return ko.utils.arrayFilter(self.availableItems(), function (item) {
return item.isSelected();
});
});
It's because you give an array to the binding checked while it's supposed to be a value comparable to true or false (like undefind or an empty string).
I would use a function checking if the $data is in your array and returning a boolean to your binding.
Something like that!

Using Backbone collections in the right way to pass them in the sub view and then in the template engine

I'm trying understand how to work with Backbone collections and to pull them inside the relative template engine created by a sub view.
This is the logic i tried in my app:
My ajax request returns me this object:
{
"products":[
{
"id":"43",
"text":"Sunset Chips",
"image":"43.png"
},{
"id":"107",
"text":"Pringles Hot & Spicy",
"image":"107.png"
}
],
"brands":[
{
"id":"132",
"text":"P&G",
"image":"132.png"
},{
"id":"27",
"text":"Kinder",
"image":"27.png"
}
]
}
I grab it with jQuery's $.ajax method and manage it for my Backbone app here in my view:
<script type="text/javascript">
var search = {};
search.app = {};
search.app.id = "#search-results";
search.product = {};
search.product.defaults = {
id:0,
text:"<?php echo __('No results here');?>",
image:"<?php echo $this->webroot;?>files/product/default.png",
};
$(function(){
var SearchApp = new Search.Views.App({
id:"#search-results"
});
var ProductList = new Search.Collections.Products();
var subView;
function parseResults (response, search) {
for (var i = response.products.length - 1; i >= 0; i--) {
ProductList.add([new Search.Models.Product(response.products[i])]);
};
subView = new Search.Views.Product ({
collection:ProductList,
id:"#product-results",
template:"#results-product-template" // solo in this.options.template
});
updateResults();
}
function updateResults () {
console.log('updateResults: Ritorno il risultato quando hunter riceve una risposta dal server');
if ($('#search-results').length == 0) {
$('div.main > section:first-child').before('<section id="search-results"></section>');
}
SearchApp.renderProductCollection(subView);
}
$('#search-results .close').on('click', function () {
$('#search-results').animate({height:0}, 500, function () {
$(this).remove();
})
});
var callbacks = {
on_response:parseResults // function presente in backbone.search.js
};
$('#hunter').hunter({url:'<?php echo $this->request->base; ?>/searches/default_search', callback:callbacks, ajax_params:{limit:10, term:'%%'}});
});
</script>
This is my Backbone application:
var Search = {
Models: {},
Collections: {},
Views: {},
Templates:{}
}
Search.Models.Product = Backbone.Model.extend({
defaults: search.product.defaults || {},
initialize:function () {
console.log("initialize Search.Models.Product");
this.on("change", function (){
console.log("chiamato evento change del Model Search.Models.Product");
});
this.on("change:text", function () {
console.log("chiamato evento change:text del Model Search.Models.Product");
});
}
});
Search.Collections.Products = Backbone.Collection.extend({
model: Search.Models.Product,
initialize:function () {
console.log("initialize Search.Collections.Products");
console.log(this);
console.log(this.length);
console.log(this.models);
}
});
Search.Views.App = Backbone.View.extend({
initialize:function () {
console.log("initialize Search.Views.App");
this.$prd = this.$('#product-results');
},
render:function () {
console.log("render Search.Views.App");
},
renderProductCollection:function (subView) {
console.log("Search.Views.App > renderProductCollection");
console.log('subView.getTemplate() => ' + subView.getTemplate());
$(this.id).html(subView.getTemplate());
}
});
Search.Views.Product = Backbone.View.extend({
initialize:function () {
console.log("initialize Search.Views.Product");
},
getTemplate:function (data) {
if (data == null || data == undefined) {
data = this.collection.toJSON() || this.model.toJSON();
}
var template = Handlebars.compile($(this.options.template).html());
console.log(data);
return '<ul id="product-results" class="w-1-4">' + template(data) + '</ul>';
},
render:function () {
console.log("render Search.Views.Product");
return this;
}
});
The Handlesbar template is simply this:
<ul class="w-1-4">
<li>
<b>Products</b>
</li>
{{#each products}}
<li>
<a href="{{url}}">
<div class="origin {{type}}" title="{{name}}"><img src="'.$this->webroot.'img/icons/16/origin/{{icon}}"></div>
</a>
<div>
{{name}}
{{#support}}{{support.name}}{{/support}}
</div>
</li>
{{/each}}
</ul>
My problem is when I try to parse the data inside the Handlesbar template, since I've parsed my data inside the sub view collection, I have an Array structured like this:
[
{
"id":"43",
"text":"Sunset Chips",
"image":"43.png"
},{
"id":"107",
"text":"Pringles Hot & Spicy",
"image":"107.png"
}
]
With this data I don't have the products object anymore due to the parseResults in the view, where I put the ajax inside the collection.
How can I parse the products array without products prop name, or how I can keep the data in the right way?
I know in my app I can do something like this to solve the problem:
var container = new array();
container['products'] = this.collection.toJSON();
data = container;
var template = Handlebars.compile($(this.options.template).html());
return '<ul id="product-results" class="w-1-4">' + template(data) + '</ul>';
But is this the right way or I'm missing something?
You do not need to use $.ajax if you are working with Backbone.
Use collection.fetch
http://backbonejs.org/#Collection-fetch
Also looks like the server is not giving you the answer as you need, so Backbone has its own parse:
http://backbonejs.org/#Collection-parse
Also I'm a fan of logic less template. So the iteration through the collection must be in the view and not in the template. Also please check this(specially point two):
http://ozkatz.github.io/avoiding-common-backbonejs-pitfalls.html
Use a collection in the right way, then to render it, in the view you could listen to the add or reset event and populate the collection in that way.
this.collection.on('add', this.render); //get model by model
this.collection.on('reset', this.render); //get all models (you need to specify in the fetch call a parameter {reset : true}

Categories