Gridstack not updating on delete - javascript

Here is my example of Gridstack layout that uses Knockout bindings. The problem is that my view doesn't get updated based on the model, when it should.
After pressing Delete me the console output shows that the widgets observable array gets updated correctly, while the view doesn't. The cause seems to be on this line (which is not being called):
ko.utils.domNodeDisposal.addDisposeCallback(item, function () {
self.grid.removeWidget(item);
});
As far as I know, the foreach binding should update automatically, why it doesn't?
var ViewModel = function() {
var self = this;
self.grid = null;
self.widgets = ko.observableArray([{
x: 0,
y: 0,
width: 1,
height: 1
}, {
x: 0,
y: 1,
width: 1,
height: 1
}]);
self.deleteWidget = function(item) {
console.log("widgets before", self.widgets());
self.widgets.remove(item);
console.log("widgets after", self.widgets());
return false;
};
self.afterAddWidget = function(items) {
if (self.grid == null) {
self.grid = $('.grid-stack').gridstack({
auto: false
}).data('gridstack');
}
var item = _.find(items, function(i) {
return i.nodeType == 1
});
self.grid.addWidget(item);
ko.utils.domNodeDisposal.addDisposeCallback(item, function() {
self.grid.removeWidget(item);
});
};
};
ko.applyBindings(new ViewModel());
.grid-stack {
background: lightgoldenrodyellow;
}
.grid-stack-item-content {
color: #2c3e50;
text-align: center;
background-color: #18bc9c;
}
<link rel="stylesheet" href="https://raw.githubusercontent.com/troolee/gridstack.js/master/dist/gridstack.css" />
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js" type="text/javascript"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.0/jquery-ui.js" type="text/javascript"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js" type="text/javascript"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.5.0/lodash.min.js" type="text/javascript"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js" type="text/javascript"></script>
<script type="text/javascript" src="https://rawgit.com/troolee/gridstack.js/master/dist/gridstack.js"></script>
<div class="grid-stack" data-bind="foreach: {data: widgets, afterRender: afterAddWidget}">
<div class="grid-stack-item" data-bind="attr: {'data-gs-x': $data.x, 'data-gs-y': $data.y, 'data-gs-width': $data.width, 'data-gs-height': $data.height, 'data-gs-auto-position': $data.auto_position}">
<div class="grid-stack-item-content">
<button data-bind="click: $root.deleteWidget">Delete me</button>
</div>
</div>
</div>

The problem was actually caused by an extra space character between </div> closing tags. The example warns about that. In my case it was inserted automatically by the code formatter, so it went unnoticed. The line in the HTML template should be </div></div><!-- <---- NO SPACE BETWEEN THESE CLOSING TAGS --> with NO space between the </div></div>
var ViewModel = function() {
var self = this;
self.grid = null;
self.widgets = ko.observableArray([{
x: 0,
y: 0,
width: 1,
height: 1
}, {
x: 0,
y: 1,
width: 1,
height: 1
}]);
self.deleteWidget = function(item) {
console.log("widgets before", self.widgets());
self.widgets.remove(item);
console.log("widgets after", self.widgets());
return false;
};
self.afterAddWidget = function(items) {
if (self.grid == null) {
self.grid = $('.grid-stack').gridstack({
auto: false
}).data('gridstack');
}
var item = _.find(items, function(i) {
return i.nodeType == 1
});
self.grid.addWidget(item);
ko.utils.domNodeDisposal.addDisposeCallback(item, function() {
self.grid.removeWidget(item);
});
};
};
ko.applyBindings(new ViewModel());
.grid-stack {
background: lightgoldenrodyellow;
}
.grid-stack-item-content {
color: #2c3e50;
text-align: center;
background-color: #18bc9c;
}
<link rel="stylesheet" href="https://raw.githubusercontent.com/troolee/gridstack.js/master/dist/gridstack.css" />
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js" type="text/javascript"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.0/jquery-ui.js" type="text/javascript"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js" type="text/javascript"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.5.0/lodash.min.js" type="text/javascript"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js" type="text/javascript"></script>
<script type="text/javascript" src="https://rawgit.com/troolee/gridstack.js/master/dist/gridstack.js"></script>
<div class="grid-stack" data-bind="foreach: {data: widgets, afterRender: afterAddWidget}">
<div class="grid-stack-item" data-bind="attr: {'data-gs-x': $data.x, 'data-gs-y': $data.y, 'data-gs-width': $data.width, 'data-gs-height': $data.height, 'data-gs-auto-position': $data.auto_position}">
<div class="grid-stack-item-content">
<button data-bind="click: $root.deleteWidget">Delete me</button>
</div>
</div></div><!-- <---- NO SPACE BETWEEN THESE CLOSING TAGS -->

Gridstack is a DOM-controlling widget. You need some kind of binding handler to make Knockout play nicely with DOM-controlling widgets.
It looks like maybe you're working from this example. It uses components in a way that is sort of a built-in binding handler. It seems to work, but I recommend putting DOM-manipulation where it goes: in the binding handlers.
Update: Here is your example with the gridstack code put into a binding handler. It simply wraps a foreach binding handler and adds the afterRender option to it in the update. Now the viewmodel looks like a viewmodel, and you would be able to handle multiple gridstacks on a page without $(.grid-stack) picking the wrong one.
ko.bindingHandlers.gridstack = {
init: function(element, valueAccessor, allBindingsAccessor, data, context) {
ko.bindingHandlers.foreach.init(element, valueAccessor, allBindingsAccessor, data, context);
return {
controlsDescendantBindings: true
};
},
update: function(element, valueAccessor, allBindingsAccessor, data, context) {
var widgets = valueAccessor(),
grid = $(element).gridstack().data('gridstack'),
afterAddWidget = function(items) {
var item = _.find(items, function(i) {
return i.nodeType === 1;
});
grid.addWidget(item);
ko.utils.domNodeDisposal.addDisposeCallback(item, function() {
grid.removeWidget(item);
});
},
newVA = function() {
return {
data: widgets,
afterRender: afterAddWidget
};
};
ko.bindingHandlers.foreach.update(element, newVA, allBindingsAccessor, data, context);
}
};
var ViewModel = function() {
var self = this;
self.grid = null;
self.widgets = ko.observableArray([{
x: 0,
y: 0,
width: 1,
height: 1
}, {
x: 0,
y: 1,
width: 1,
height: 1
}]);
self.deleteWidget = function(item) {
self.widgets.remove(item);
};
};
ko.applyBindings(new ViewModel());
.grid-stack {
background: lightgoldenrodyellow;
}
.grid-stack-item-content {
color: #2c3e50;
text-align: center;
background-color: #18bc9c;
}
<link rel="stylesheet" href="https://raw.githubusercontent.com/troolee/gridstack.js/master/dist/gridstack.css" />
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js" type="text/javascript"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.0/jquery-ui.js" type="text/javascript"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js" type="text/javascript"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.5.0/lodash.min.js" type="text/javascript"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-debug.js" type="text/javascript"></script>
<script type="text/javascript" src="https://rawgit.com/troolee/gridstack.js/master/dist/gridstack.js"></script>
<div class="grid-stack" data-bind="gridstack: widgets">
<div class="grid-stack-item" data-bind="attr: {'data-gs-x': $data.x, 'data-gs-y': $data.y, 'data-gs-width': $data.width, 'data-gs-height': $data.height, 'data-gs-auto-position': $data.auto_position}">
<div class="grid-stack-item-content">
<button data-bind="click: $parent.deleteWidget">Delete me</button>
</div>
</div></div><!-- <---- NO SPACE BETWEEN THESE CLOSING TAGS -->

Related

The javascript onDrop event is not executing

The javascript onDrop event is not executing when an object is dropped into a drop zone. I've tried re-writing this in at least half a dozen ways with no luck and no error message either. I was looking around for reasons that would prevent the ondrop event from executing and found:
HTML5/Canvas onDrop event isn't firing?
and
https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations#droptargets
To the best of my knowledge, I've accomplished all three requirements to trigger an ondrop event. I implemented both the ondragenter and ondragover events which both contain event.preventDefault(); and return false;. I also have event.preventDefault(); in the ondrop event.
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.lists = [{name: "A", list: ["A Object 1", "A Object 2", "A Object 3"]},
{name: "B", list: ["B Object 1", "B Object 2", "B Object 3"]}];
$scope.drag_index = null;
$scope.drag_obj = null;
});
var dragging = false;
function toggle_dz() {
$(".drop-it").toggle();
}
function get_gbls(cs) {
while (cs.$parent) {
cs = cs.$parent;
}
return cs;
}
app.directive('zoneIt', function(){
return {
restrict: 'A',
link: function(scope, element, attrs){
$(element).on('dragenter',function(event){
event.preventDefault();
console.log("Drag Enter!");
return false;
});
$(element).on('dragover',function(event){
event.preventDefault();
console.log("Drag Over!");
return false;
});
}
};
});
app.directive('dragIt', function(){
return {
restrict: 'A',
link: function(scope, element, attrs){
$(element).on('drag',function(event){
var scope = angular.element(event.target).scope();
get_gbls(scope).drag_index = scope.$index;
get_gbls(scope).drag_obj = scope.obj;
if (!dragging) {
toggle_dz();
dragging = true;
}
});
$(element).on('drop',function(event){
event.preventDefault();
alert("DROPPED!");
});
$(element).on('dragend',function(event){
toggle_dz();
dragging = false;
var scope = angular.element(event.target).scope();
get_gbls(scope).drag_index = null;
get_gbls(scope).drag_obj = null;
get_gbls(scope).$apply();
});
}
};
});
.zone-it {
height: 150px;
width: 90%;
background-color: #1B6269;
border: 10px dashed #4D3A44;
border-radius: 10px;
margin: 10px 5%;
padding: 15px;
font-size: 100px;
color: #4D3A44;
text-shadow: 0 0 10px black;
}
.zone-it span {
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<div class="container-fluid" ng-app="myApp" ng-controller="myCtrl">
<h2>Angular Drag n Drop</h2>
<div class="col-xs-6" ng-repeat="list in lists">
<h3>{{list.name}}</h3>
<hr>
<div class="col-xs-12 drop-it zone-it" zone-it style="display: none;"><span class="text-center glyphicon glyphicon-download"></span></div>
<div class="drop-it container-it">
<div class="col-sm-6 col-md-4 col-lg-3" ng-repeat="obj in list.list">
<div class="thumbnail drag-it" draggable="true" drag-it style="cursor:pointer">
<div class="caption text-center">{{obj}}</div>
</div>
</div>
</div>
</div>
</div>
Here is a fiddle of my code: https://jsfiddle.net/Ashkeelun/2uLwd077/1/
So, I figured it out. The ondrop event belongs with the drop zone not the item being dragged.
https://jsfiddle.net/Ashkeelun/2uLwd077/4/
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.lists = [{name: "A", list: ["A Object 1", "A Object 2", "A Object 3"]},
{name: "B", list: ["B Object 1", "B Object 2", "B Object 3"]}];
$scope.drag_index = null;
$scope.drag_obj = null;
});
var dragging = false;
function toggle_dz() {
$(".drop-it").toggle();
}
function get_gbls(cs) {
while (cs.$parent) {
cs = cs.$parent;
}
return cs;
}
app.directive('zoneIt', function(){
return {
restrict: 'A',
link: function(scope, element, attrs){
$(element).on('dragenter',function(event){
event.preventDefault();
console.log("Drag Enter!");
return false;
});
$(element).on('dragover',function(event){
event.preventDefault();
console.log("Drag Over!");
return false;
});
$(element).on('drop',function(event){
event.preventDefault();
alert("DROPPED!");
});
}
};
});
app.directive('dragIt', function(){
return {
restrict: 'A',
link: function(scope, element, attrs){
$(element).on('drag',function(event){
var scope = angular.element(event.target).scope();
get_gbls(scope).drag_index = scope.$index;
get_gbls(scope).drag_obj = scope.obj;
if (!dragging) {
toggle_dz();
dragging = true;
}
});
$(element).on('dragend',function(event){
toggle_dz();
dragging = false;
var scope = angular.element(event.target).scope();
get_gbls(scope).drag_index = null;
get_gbls(scope).drag_obj = null;
get_gbls(scope).$apply();
});
}
};
});
.zone-it {
height: 150px;
width: 90%;
background-color: #1B6269;
border: 10px dashed #4D3A44;
border-radius: 10px;
margin: 10px 5%;
padding: 15px;
font-size: 100px;
color: #4D3A44;
text-shadow: 0 0 10px black;
}
.zone-it span {
display: block;
}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<div class="container-fluid" ng-app="myApp" ng-controller="myCtrl">
<h2>Angular Drag n Drop</h2>
<div class="col-xs-6" ng-repeat="list in lists">
<h3>{{list.name}}</h3>
<hr>
<div class="col-xs-12 drop-it zone-it" zone-it style="display: none;"><span class="text-center glyphicon glyphicon-download"></span></div>
<div class="drop-it container-it">
<div class="col-sm-6 col-md-4 col-lg-3" ng-repeat="obj in list.list">
<div class="thumbnail drag-it" draggable="true" drag-it style="cursor:pointer">
<div class="caption text-center">{{obj}}</div>
</div>
</div>
</div>
</div>

Gridstact: make widget static and type error

I am a newer for javascript and gridstack. My job is creating a dashboard based on gridstack. The most close example for my goal is demo example knockout.html. Also, I need add on several new features based on this example. Here is the problem I am facing.
I need add a dropdown menu to each widget and it is added to the template, please refer the code attached below. Now I am using a button for toggling the drop menu. Is that possible for me to use the mouse right click to show the dropdown menu?
When I added the method named toggleDropdownMenu, I am getting a type error as "TypeError: this.toggleDropdownMenu is not a function", is this function should be defined somewhere?
I want to make specified widget to be a static. I added a attr settings when create the widget like staticGrid: true but does not work. How can fix this?
For each dash board, the dropdown menu is different for each widget. How can I attach dropdown menu with different items for each widget?
Please refer the code... Thank you very much for any advices!
<!DOCTYPE html>
<html lang="en">
<head>
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dash board demo</title>
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="./dist/gridstack.css"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.0/jquery-ui.js"></script>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.5.0/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="./dist/gridstack.js"></script>
<style type="text/css">
.grid-stack {
background: lightgoldenrodyellow;
}
.grid-stack-item-content {
color: #2c3e50;
text-align: center;
background-color: #18bc9c;
}
.grid-stack-item-content button{
background-color: Transparent;
background-repeat:no-repeat;
border: none;
cursor:pointer;
overflow: hidden;
outline:none;
color: white;
position: relative;
}
.grid-stack-item-content .dropdown-submenu {
position: relative;
}
.grid-stack-item-content .dropdown-submenu .dropdown-menu {
top: 0;
left: 100%;
margin-top: -1px;
}
</style>
</head>
<body>
<div class="container-fluid">
<h1>Dashboard Demo</h1>
<div>
<button data-bind="click: addNewWidget">Add new widget</button>
</div>
<br>
<div data-bind="component: {name: 'dashboard-grid', params: $data}"></div>
</div>
<script type="text/javascript">
ko.components.register('dashboard-grid', {
viewModel: {
createViewModel: function (controller, componentInfo) {
var ViewModel = function (controller, componentInfo) {
var grid = null;
this.widgets = controller.widgets;
this.afterAddWidget = function (items) {
if (grid == null) {
grid = $(componentInfo.element).find('.grid-stack').gridstack({
auto: false
}).data('gridstack');
}
var item = _.find(items, function (i) { return i.nodeType == 1 });
grid.addWidget(item);
ko.utils.domNodeDisposal.addDisposeCallback(item, function () {
grid.removeWidget(item);
});
};
};
return new ViewModel(controller, componentInfo);
}
},
template:
[
'<div class="grid-stack" data-bind="foreach: {data: widgets, afterRender: afterAddWidget}">',
' <div class="grid-stack-item" data-bind="attr: {\'data-gs-x\': $data.x, \'data-gs-y\': $data.y, \'data-gs-width\': $data.width, \'data-gs-height\': $data.height, \'data-gs-auto-position\': $data.auto_position}">',
' <div class="grid-stack-item-content">',
//' <button data-bind="click: $root.deleteWidget">Delete me</button>',
' <div class="dropdown">',
' <button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown">Click me',
' <span class="caret"></span>',
' </button>',
' <ul class="dropdown-menu">',
' <li data-bind="click: $root.deleteWidget"><a tabindex="-1" href="#">Delete</a></li>',
' <li><a tabindex="-1" href="#">Change Color</a></li>',
' <li><a tabindex="-1" href="#">Copy to</a></li>',
' <li class="dropdown-submenu">',
' <a class="plan" tabindex="-1" href="#">New dropdown <span class="caret"></span></a>',
' <ul class="dropdown-menu">',
' <li><a tabindex="-1" href="#">2nd level dropdown</a></li>',
' <li><a tabindex="-1" href="#">2nd level dropdown</a></li>',
' <li class="dropdown-submenu">',
' <a class="test" href="#">Another dropdown <span class="caret"></span></a>',
' <ul class="dropdown-menu">',
' <li>3rd level dropdown</li>',
' <li>3rd level dropdown</li>',
' </ul>',
' </li>',
' </ul>',
' </li>',
' </ul>',
' </div>',
' </div>',
' </div>',
'</div> '
].join('')
});
$(function () {
var Controller = function (widgets) {
var self = this;
this.widgets = ko.observableArray(widgets);
this.addNewWidget = function (i) {
this.widgets.push({
x: 0,
y: 0,
id: 'widget'+i,
width: i== 0 ? 2 : 1, //Math.floor(1 + 3 * Math.random()),
height: i== 0 ? 2 : 1, //Math.floor(1 + 3 * Math.random()),
auto_position: true
});
return false;
};
this.deleteWidget = function (item) {
self.widgets.remove(item);
return false;
};
/**
this.toggleDropdownMenu(function(){
$('.dropdown-submenu a.test').on("click", function(e)
{
$(this).next('ul').toggle();
e.stopPropagation();
e.preventDefault();
});
});
**/
};
var widgets = [
{x: 0, y: 0, width: 2, height: 2, staticGrid: true },
{x: 2, y: 0, width: 1, height: 1},
{x: 3, y: 1, width: 2, height: 2},
{x: 1, y: 2, width: 1, height: 1}
];
var controller = new Controller(widgets);
ko.applyBindings(controller);
});
</script>
</body>
</html>

2 containers cancel eachother

i would like to know how i can prevent two containers from canceling. the codes are almost the same i just changed a few things it doesn't mater which one i put first but the second one is not working if i switched them around the one that i put first works but not the second one. I'm using toggle to display one at a time. I'm just going to post a small part of my code.
JavaScript for first part
<script>
var z = 1; //value to make div overlappable
$('#addText').click(function (e) {
/** Make div draggable **/
$('<div />', {
class: 'ui-widget-content',
appendTo: '.container',
draggable: {
containment: 'parent',
start: function( event, ui ) {
$(this).css('z-index', ++z);
}
}
});
});
$(document).on("dblclick", '.text', function()
{
$(this).hide(); $(this).closest('.item').find('.edit_text').val($(this).text()).show();
});
$(document).on("click", ".edit_text", function()
{
return false;
});
$(document).on("click", function()
{
var editingText = $('.edit_text:visible');
if (editingText.length)
{
editingText.hide();
editingText.closest('.item').find('.text').text($(editingText).val()).show();
}
});
var count = 1;
var selectedDraggable;
ko.bindingHandlers.draggable={
init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
$(element).draggable();
$(element).addClass('item' + count);
count++;
$(element).on('click', function () {
selectedDraggable = $(this);
})
}
};
var vm=function(){
var self=this;
self.items=ko.observableArray();
self.textContent = ko.observable('');
self.init=function(){
self.items([]);
}
self.remove=function(item){
console.log(item);
self.items.remove(item);
}
self.addNew = function() {
self.items.push( self.textContent() );
self.textContent('');
}
self.init();
}
ko.applyBindings(new vm());
JavaScript for second part
var z = 1; //value to make div overlappable
$('#addText').click(function (e) {
/** Make div draggable **/
$('<div />', {
class: 'ui-widget-content',
appendTo: '.container4',
draggable: {
containment: 'parent',
start: function( event, ui ) {
$(this).css('z-index', ++z);
}
}
});
});
$(document).on("dblclick", '.text1', function()
{
$(this).hide(); $(this).closest('.item1').find('.edit_text1').val($(this).text()).show();
});
$(document).on("click", ".edit_text1", function()
{
return false;
});
$(document).on("click", function()
{
var editingText = $('.edit_text1:visible');
if (editingText.length)
{
editingText.hide();
editingText.closest('.item1').find('.text1').text($(editingText).val()).show();
}
});
var count = 1;
var selectedDraggable;
ko.bindingHandlers.draggable={
init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
$(element).draggable();
$(element).addClass('item1' + count);
count++;
$(element).on('click', function () {
selectedDraggable = $(this);
})
}
};
var vm=function(){
var self=this;
self.items1=ko.observableArray();
self.textContent1 = ko.observable('');
self.init=function(){
self.items1([]);
}
self.remove=function(item){
console.log(item);
self.items1.remove(item);
}
self.addNew1 = function() {
self.items1.push( self.textContent1() );
self.textContent1('');
}
self.init();
}
ko.applyBindings(new vm());
toggle
$("#show_first").click(function(){
$(".firstdiv").toggle();
$(".seconddiv").hide();
});
$("#show_second").click(function(){
$(".secoddiv").toggle();
$(".firstdiv").hide();
});
HTML for toggle
<button type="button" id="show_first">Display Front</button>
<button type="button" id="show_second">Display Back</button>
HTML for container and input text (first)
<div class="firstdiv"><center>Front</center>
<div class="container1" style=" float: left;" >
<p align="center"><textarea data-bind="value: textContent" Placeholder="Type text to append" rows="4" cols="21"></textarea>
<button type="button" data-bind="click: addNew">Create</button></p>
<div id="box" class="container" style="float:left;">
<div data-bind="foreach:items" class="fix_backround">
<div class="item" data-bind="draggable:true,droppable:true">
<center><span class="text" data-bind="text:$data"></span><input class="edit_text"/></center></div></div></div></div></div>
HTML for container and input text (second)
<div class="seconddiv"><center>second</center>
<div class="container3" style=" float: left;" >
<p align="center"><textarea data-bind="value: textContent1" Placeholder="Type text to append" rows="4" cols="21"></textarea>
<button type="button" data-bind="click: addNew1">Create</button></p></div>
<div id="box1" class="container4" style="float:left;">
<div data-bind="foreach:items1" class="fix_backround1">
<div class="item1" data-bind="draggable:true,droppable:true">
<center><span class="text1" data-bind="text:$data"></span><input class="edit_text1"/></center></div></div></div></div></div>
Script
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/knockout/2.3.0/knockout-min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/knockout/2.3.0/knockout-min.js"></script>
<link rel="stylesheet"href="http://code.jquery.com/ui/1.9.2/themes/base/jquery-ui.css" />
<script src="http://code.jquery.com/ui/1.9.2/jquery-ui.js"></script>
<link rel="stylesheet"href="//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css">
<script src="//code.jquery.com/jquery-1.10.2.js"></script>
<script src="//code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
<script src="http://circletype.labwire.ca/js/circletype.js"></script><script src="http://tympanus.net/Development/Arctext/js/jquery.arctext.js"></script>
You are including jQuery, Knockout, and jQuery-UI each twice. That's probably not the problem, but it's not good.
You're using jQuery to control which block displays, and that's a Knockout no-no. It is Knockout's job to manipulate the DOM. Have a look at Knockout templates, or the if binding for ways of controlling what displays.
It looks like you have a typo in your toggle js for the seconddiv class. You're missing an 'n' in your jquery call for it: secoddiv. If this code is straight from the source that may be the problem.

Polymer add Html tags with properties

My Polymer element displays some properties it gets passed as attributes. The content property may contain some html tags like <br> or <p>. The problem I'm facing with, is, that Polymer doesn't add the tags to the DOM tree, instead it prints them like normal text. Is there a way to force "DOM tree adding"?
The whole element:
<link rel="import" href="paper-toolbar/paper-toolbar.html">
<link rel="import" href="iron-collapse/iron-collapse.html">
<link rel="import" href="paper-material/paper-material.html">
<dom-module id="card-element" is="auto-binding">
<style>
#contentWrapper {
padding: 10px 15px;
}
#toolbar {
--paper-toolbar-background: #607D8B;
--paper-toolbar: {
font-size: 125%;
opacity: 0.9;
};
}
.maxWidth {
width: 100%;
}
</style>
<template>
<paper-material elevation="2" class="maxWidth" id="card" animatedShadow="1">
<paper-material elevation="1" class="maxWidth">
<paper-toolbar on-click="toggleCollapse" id="toolbar" justify="justified">
<span class="title">{{convertedDate}}</span><span class="title">{{fach}}</span>
</paper-toolbar>
</paper-material>
<iron-collapse id="collapse">
<div id="contentWrapper">
<span>{{content}}</span>
</div>
</iron-collapse>
</paper-material>
</template>
<script>
Polymer({
is: "card-element",
properties: {
opened: {
type: Boolean,
value: false
},
fach: {
type: String,
value: "u-oh an Error"
},
content: {
type: String,
value: "u-oh an Error"
}
},
toggleCollapse: function() {
if(this.opened) {
this.$.collapse.hide();
this.$.card.elevation = "2";
this.opened = false;
}
else {
this.$.collapse.show();
this.$.card.elevation = "5";
this.opened = true;
}
},
ready: function() {
var date = new Date(this.datum);
this.convertedDate = date.getDate() + "." + (date.getMonth() + 1) + "." + date.getFullYear();
}
});
</script>
</dom-module>
Polymer not allowed html to prevent XSS attack
But you can do this
<dom-module id="html-echo">
<style>
:host {
display: block;
}
</style>
<template>
</template>
</dom-module>
<script>
(function () {
Polymer({
is: 'html-echo',
properties: {
html: {
type: String,
observer: '_htmlChanged'
}
},
_htmlChanged: function (neo) {
// WARNING: potential XSS vulnerability if `html` comes from an untrusted source
this.innerHTML = neo;
}
});
})();
</script>
Use like
<html-echo html="[[htmlText]]"></html-echo>

Knockoutjs: Invoking function of parent component from child component

Problem:
I'm trying to build a dashboard of widgets. Each widget will have a delete button on its header. When clicked on this button, corresponding widget have to disappear.
How I designed:
I have two knockout components.
my-widget-list:
VO will have an observableArray of widget objects.
my-widget:
VO will have details to display within the widget.
Note: For simplicity, I'm replacing the widget object with just numbers.
ko.components.register('my-widget-list', {
viewModel : function(params) {
var self = this;
self.values = ko.observableArray([10,20,30,40,50]);
self.deleteWidget = function(obj)
{
self.values.remove(obj);
}
},
template: {element: 'my-widget-list-template'}
});
ko.components.register('my-widget', {
viewModel : function(params) {
var self = this;
self.value = params.value;
},
template: {element: 'my-widget-template'}
});
ko.applyBindings({});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<my-widget-list></my-widget-list>
<script id="my-widget-list-template" type="text/html">
<div data-bind="foreach:values">
<my-widget params="value: $data"></my-widget><br>
</div>
</script>
<script id="my-widget-template" type="text/html">
<span data-bind="text: value"></span>
<button data-bind="click: $parent.deleteWidget">Delete</button>
</script>
Now, I want to invoke my-widget-list's deleteWidget function when the button is clicked.
I have thought about
Passing the parent view model reference into the child
Passing the parent function in the params attribute of the child component as a callback
But I wish to know from experts what's the best way to achieve this.
JsFiddle Link
Thanks in advance
You can pass in the parent as a param to the child:
ko.components.register('my-widget-list', {
viewModel : function(params) {
var self = this;
self.values = ko.observableArray([10,20,30,40,50]);
self.deleteWidget = function(obj) {
self.values.remove(obj);
}
},
template: {element: 'my-widget-list-template'}
});
ko.components.register('my-widget', {
viewModel : function(params) {
var self = this;
self.value = params.value;
self.remove = function () {
params.parent.deleteWidget(self.value);
};
},
template: {element: 'my-widget-template'}
});
ko.applyBindings({});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<my-widget-list></my-widget-list>
<script id="my-widget-list-template" type="text/html">
<div data-bind="foreach:values">
<my-widget params="value: $data, parent: $parent"></my-widget><br>
</div>
</script>
<script id="my-widget-template" type="text/html">
<span data-bind="text: value"></span>
<button data-bind="click: remove">Delete</button>
</script>
But I'm not sure if that is a good idea, as it needlessly couples the child to the parent.
I'd recommend implementing the "remove" button in the parent, i.e. in <my-widget-list>, this way the widget can exist without a widget-list (or in a differently structured one) while the widget-list is in control of its children.
Compare window managers: They work the same way. The window manager draws the frame and the minimize/maximize/close buttons, while the window contents is drawn by the respective child process. That logic makes sense in your scenario as well.
Alternative implementation with removeWidget control in the parent:
ko.components.register('my-widget-list', {
viewModel : function(params) {
var self = this;
self.values = ko.observableArray([10,20,30,40,50]);
self.deleteWidget = function(obj) {
self.values.remove(obj);
}
},
template: {element: 'my-widget-list-template'}
});
ko.components.register('my-widget', {
viewModel : function(params) {
var self = this;
self.value = params.value;
},
template: {element: 'my-widget-template'}
});
ko.applyBindings({});
.widget-container {
position: relative;
display: inline-block;
padding: 10px 5px 5px 5px;
margin: 0 5px 5px 0;
border: 1px solid silver;
border-radius: 2px;
min-width: 40px;
}
.widget-buttons {
position: absolute;
top: 2px;
right: 2px;
}
.widget-buttons > button {
font-size: 2px;
padding: 0;
height: 15px;
width: 15px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<my-widget-list></my-widget-list>
<script id="my-widget-list-template" type="text/html">
<div class="widget-list" data-bind="foreach:values">
<div class="widget-container">
<div class="widget-buttons">
<button data-bind="click: $parent.deleteWidget">X</button>
</div>
<my-widget params="value: $data"></my-widget>
</div>
</div>
</script>
<script id="my-widget-template" type="text/html">
<div class="widget">
<span data-bind="text: value"></span>
</div>
</script>

Categories