[enhancement] - Don't clear terminal on resize #148

Closed
drewbaker opened this Issue Feb 25, 2014 · 20 comments

Projects

None yet

2 participants

@drewbaker

Hey Jakob,

It would be great to be able to set resize:false, and have the terminal not clear it's contents on resize.

I'm loading in content to the divs created by an empty echo using PJAX, and they get cleared by resize function.

Thanks,
Drew

@jcubic
Owner
jcubic commented Feb 25, 2014

Can you show code (or demo) where you use the terminal? And what is PJAX?

@drewbaker

Hey Jakob,

Sorry to lag on this! I built you a demo of PJAX working with terminal, but it clears when you resize the window.
http://labs.funkhausdesign.com/examples/terminal/terminal_pjax.html

PJAX is this:
https://github.com/defunkt/jquery-pjax

It's a great way of using HTML5 pushState tech to have no page loads, but have deep linkable URL's.

If you send me your email address, I can show you the development site that I have this tech working on for one of our clients. It's a super advanced version of Terminal using PJAX. My email is drew@funkhaus.us.

Thanks!

@jcubic
Owner
jcubic commented Mar 22, 2014

You're not using terminal and Pjax as it should be used. As for terminal use api provided. As for History API I sugest differnt library that alow for callbacks. Also in your code you don't have full change on back button only url and image change the command entered stay the same.

In order to have proper state you need to use export_view / import_view functions. Here is the code I've came up with (that use HTML5-History-API library which is just a polyfill so it use raw HistoryAPI)

$(function() {
    var save_state = [];
    var terminal = $('#term').terminal(function(command, term) {
        var cmd = $.terminal.splitCommand(command);
        if (cmd.name == 'open') {
            term.pause();
            $.get(cmd.args[0], function(result) {
                term.echo(result, {raw:true}).resume();
                save_state.push(term.export_view());
                history.pushState(save_state.length-1, null, cmd.args[0]);
            }, 'text');
        } else {
            // store all other commands
            save_state.push(term.export_view());
            history.pushState(save_state.length-1, null, '/' + cmd.name + '/' + cmd.args.join('/'));
        }
    });
    save_state.push(terminal.export_view());
    $(window).on('popstate', function(e) {
        if (save_state.length) {
            terminal.import_view(save_state[history.state || 0]);
        }
    });
    $('.links a').click(function(e) {
        terminal.exec('open "' + $(this).attr('href') + '"');
        return false;
    });
});

save_state store exported view of the terminal and index in history.state. If history.state is null it mean that it's first page so code can grab 0 index (that exported after terminal creation).
The above command store in History API all commands that you type into the terminal. (I think that I put that code on examples).

@jcubic
Owner
jcubic commented Mar 22, 2014

I can add this to the terminal, it can store state just before next command be executed and it can store urls based on user function:

$('...').terminal(function(cmd, term) {
}, {
   historyState: true,
   historyStateUrlMapper: function(command) {
       // whatever that function return, will be stored as url, default will be
       // default can be "real url" + command.replace(/\s+/g, '/');
       // if the value is null the url will not be stored, so it will act as filter.
   }
});

So your code in this case will be just:

$('...').terminal({
    open: function(url) {
        var self = this.pause();
        $.get(url, function(result) {
            self.echo(result, {raw:true}).resume();
        }, 'text');
    }
}, {
   historyState: true,
   historyStateUrlMapper: function(command) {
       if (command.match(/^open/)) {
           return command.replace(/^[^\s]+\s*/, '');
       }
   }
});
@drewbaker

That second historyState code is great. I can see that being very useful. I will look into using that. Are you going to bake that into the main Terminal code, or is this just for my use case?

But is it possible to somehow disable Terminal from clearing all content on resize? Even just point me to the right place in the code, so I can hack it for my needs right now.

I agree that PJAX isn't great, I do wish it had proper callbacks and could return data, rather than forcing it to go to a selector.

Thanks!

@jcubic
Owner
jcubic commented Mar 22, 2014

I can make that historyState into terminal 0.9, nice feature, I don't have much features to add to new version of terminal.

to disable clear in the terminal you need to comment out that line:
https://github.com/jcubic/jquery.terminal/blob/master/js/jquery.terminal-src.js#L3815
it will never redraw terminal, but you will not have line wrapping. Clear is used to draw lines (proper lines that was echo using echo function) again.

@drewbaker

Hey Jakob,

Any ideas on when you might release 0.9? I want to start using this History feature on our project.

Thanks!
Drew

@jcubic
Owner
jcubic commented Apr 8, 2014

I can add that history feature when I find some time, and then you will be able to grab it from devel branch.

@jcubic
Owner
jcubic commented Apr 10, 2014

I've added the feature to devel branch.

@jcubic jcubic closed this Apr 10, 2014
@drewbaker

Hey Jakob,

I've been using this, and it's great! Quick question:

Say I echo some HTML via AJAX like you have above. Let's say its a grid of thumbnail images, and set the state via your new feature.

Next, I click on the thumbnail, and via AJAX I replace that thumbnail with a video. How do you recommend I deal with that state change? I can't echo out a new command, because I want that video to replace the image, not echo onto a new line.

Currently I'm just setting that history.pushState() manually on click, but I was wondering if I could do it smarter through this new Terminal feature you added?

Thanks again! This is a great plugin!

@jcubic
Owner
jcubic commented Apr 11, 2014

You don't do that with terminal. Maybe I can pushState on import_view and add flag that will prevent that to that function so popstate will be able to use import without pushing state.

Do you use import/export to change image to video? Because that is the only way to change lines dynamically that will keep terminal internal state.

@drewbaker

I don't currently use import/export. I didn't think that would let me dynamically change part of a page?

@jcubic
Owner
jcubic commented Apr 11, 2014

Export give you access to internal array of lines, that are redraw on refresh. I can prepare demo how to modify lines.

@drewbaker

Thanks! It would be great to see an example of export, change content, then import it back into view.

@drewbaker

I've been playing with this today.

I've made it so that, on click of an image, I export the view, and then I can use the HTML and edit it.

But I struggle to then import it back to the correct line. The image clicked might be 3 lines previous.

@jcubic
Owner
jcubic commented Apr 12, 2014

The code will be:

    $('.selector').terminal(function(cmd, term) {
        if (cmd == 'foo') {
            term.echo('<img src="/some/image.png" data-replace="/some/other/image.png">', {
                raw: true
            });
        }
    }).on('click', 'img', function() {
        var self = $(this);
        // get index of the line you just click on
        var index = self.parents('.terminal-output > div').index();
        // you can also use $.terminal.active()
        var terminal = self.parents('.terminal').terminal();
        var view = terminal.export_view();
        var line = view.lines[index][0]; // index 0 is a string 1 is options for the line
        view.lines[index][0] = line.replace(self.attr('src'), self.data('replace'));
        terminal.import_view(view).save_state();
    });

I came into a problem that lines in export/import was using shallow copy (lines.slice(0)) and when you modify what you exported you where changing old state, I fix that in last commit (save_state() is new API function I've added, it push history state and export_view() so you can call it if you do something outside of terminal like the click event here).

@drewbaker

If I use save_state() like you have it on the last line above, what URL does it save in the pushState?

@jcubic
Owner
jcubic commented Apr 14, 2014

In my case none, but you can pass arguments to that function save_state(title, url). (I think that title is not used at all by the browsers).

@jcubic
Owner
jcubic commented May 30, 2014

If you're interested, I think I remove historyState and use hash change, it's much better because it keep state when you refresh. Here is the code, right now outside of terminal:

$(function() {
    var state = [];
    var save = false; // don't change hash onInit
    var process_hash = true;
    var terminal = $('body').terminal($.noop, {
        onAfterCommand: function(term, command) {
            if (!$.isArray(commands)) {
                commands = [];
            }
            state.push(term.export_view());
            commands.push([state.length-1, command]);
            if (save) {
                // don't call hashchange on this change
                process_hash = false;
                location.hash = JSON.stringify(commands);
                setTimeout(function() {
                    process_hash = true;
                }, 100);
            }
        },
        onInit: function(term) {
            state.push(term.export_view());
        },
        onBlur: function() {
            return false;
        }
    });

    $(window).hashchange(function() {
        if (process_hash) {
            try {
                if (location.hash) {
                    commands = $.parseJSON(location.hash.replace(/^#/, ''));
                } else {
                    commands = [];
                }
                if (commands.length) {
                    var json_state = commands[commands.length-1];
                    if (state[json_state[0]]) {
                        terminal.import_view(state[json_state[0]]);
                    } else {
                        // don't change hash with this exec
                        save = false;
                        terminal.exec(json_state[1]);
                        // undocumented terminal fire this event
                        terminal.bind('resume.hash', function() {
                            save = true;
                            terminal.unbind('resume.hash');
                        });
                    }
                } else if (state[0]) {
                    terminal.import_view(state[0]);
                }
            } catch(e) {
                terminal.exception(e);
            }
        }
    });

    var commands;
    function exec_hash() {
        if (location.hash) {
            try {
                commands = $.parseJSON(location.hash.replace(/^#/, ''));
                $.each(commands, function(i, command) {
                    try {
                        terminal.exec(command[1]);
                    } catch(e) {
                        var cmd = $.terminal.escape_brackets(command);
                        var msg = "Error while exec with command " + cmd;
                        terminal.error(msg).error(e.stack);
                    }
                });
            } catch (e) {
                //invalid json - ignore
            }
        }
    }
    exec_hash();
    save = true;
});

It use jQuery hashchange and I've added onAfterCommand callback (I dind't push onAfterCommand into devel branch yet). I not sure if I should put that code into terminal. Maybe I should just put it as example.

@jcubic
Owner
jcubic commented May 30, 2014

Here is demo http://terminal.jcubic.pl/latest.html (it use uncommited jquery.terminal-src.js file).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment